Xây dựng SLSA Pipeline cho Ứng dụng Container: Bảo mật Chuỗi Cung ứng Phần mềm từ Source đến Registry

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

Lỗ hổng Ít ai Nhắc đến: Build Pipeline của Bạn

Vài năm trước, tôi giúp một nhóm kiểm tra lại Kubernetes deployment sau một sự cố bảo mật. Các container image của họ có tag đúng, registry đúng, tên đúng — vậy mà có gì đó đã bị can thiệp ở đâu đó giữa quá trình CI build và pod đang chạy thực tế. Thủ phạm không phải là zero-day trong code ứng dụng, mà là một bước build bị xâm phạm đã lén nhét một binary đã bị chỉnh sửa vào một Docker layer.

Sự cố đó đưa bảo mật chuỗi cung ứng lên đầu danh sách học tập của tôi — mãi mãi. Hầu hết các team DevOps vẫn xem nhẹ vấn đề này.

SolarWinds năm 2020, backdoor xz-utils năm 2024, vụ Codecov bị xâm phạm — đây không phải những trường hợp ngoại lệ hiếm gặp. Chúng là bằng chứng cho thấy kẻ tấn công giờ đây nhắm vào chính quá trình build, không chỉ ứng dụng đang chạy.

Nguyên nhân gốc rễ: Không có Chuỗi Giám sát cho Artifact

Khi bạn pull một image như myapp:v1.2.3 từ registry, thực ra bạn biết gì về nó? Với hầu hết các pipeline, câu trả lời thật lòng là: không nhiều.

  • Nó có được build từ đúng commit được tag v1.2.3 không?
  • CI runner nào đã build nó, và runner đó có sạch không?
  • Có ai hoặc script nào chỉnh sửa image giữa lúc build và lúc push không?
  • Base image và các dependency có được xác minh trước khi đưa vào không?

Không có câu trả lời cho những câu hỏi này, container registry của bạn về cơ bản chỉ là một hộp đựng binary không nhãn với mấy tờ giấy nhớ dán lên.

SLSA — Supply-chain Levels for Software Artifacts — được Google tạo ra và hiện do OpenSSF duy trì. Nó giải quyết đúng khoảng trống này. SLSA định nghĩa bốn cấp độ với mức độ kiểm soát tăng dần, mỗi cấp cung cấp đảm bảo mạnh hơn về cách phần mềm được build và liệu nó có bị can thiệp hay không.

Tổng quan các cấp độ SLSA

  • Level 1: Provenance tồn tại — metadata build được tạo ra và có thể truy cập.
  • Level 2: Provenance được ký bởi build service; lịch sử chống giả mạo.
  • Level 3: Build chạy trong môi trường độc lập, có thể kiểm toán, không có sự can thiệp của người vận hành.
  • Level 4: Review hai chiều, hermetic build — chủ yếu dành cho hạ tầng quan trọng.

Với hầu hết các container workload production, Level 2 là mục tiêu thực tế và Level 3 đáng để hướng tới với các service nhạy cảm. Đó là nội dung tôi sẽ đề cập ở đây.

So sánh các Hướng tiếp cận: Các Team Giải quyết Vấn đề này Như thế nào

Phương án A: Tin vào Tag và Cầu may

Đây là mặc định. Bạn push v1.2.3, deployment của bạn pull v1.2.3. Không có xác minh nào xảy ra. Nó hoạt động cho đến khi không hoạt động nữa — và khi không hoạt động, bạn có thể mất nhiều ngày mới phát hiện ra.

Phương án B: Ký Image Thủ công với Docker Content Trust

Docker Content Trust (DCT) dùng Notary v1 để ký image. Nó hoạt động được, nhưng chi phí vận hành cao: quản lý key phức tạp, trải nghiệm người dùng thô, và hầu hết công cụ không tích hợp gọn gàng với nó. Tôi đã thấy các team bật DCT, vật lộn với nó cả tuần, rồi lặng lẽ tắt đi.

Phương án C: Cosign + SLSA Provenance (câu trả lời đúng)

Các pipeline hiện đại kết hợp Cosign (từ Sigstore) để ký image với SLSA provenance attestation được tạo ra bởi hệ thống CI của bạn. Điều này cho bạn:

  • Bằng chứng mật mã xác nhận image được build bởi một workflow cụ thể
  • Một tài liệu provenance được ký, liên kết image với source commit của nó
  • Thực thi chính sách tại thời điểm deploy (ví dụ: với Kyverno hoặc OPA Gatekeeper)

GitHub Actions, GitLab CI và Google Cloud Build đều đã có SLSA provenance generator tích hợp sẵn. Toolchain đã trưởng thành đáng kể trong hai năm qua.

Thực hành: SLSA Level 2 cho Container với GitHub Actions

Bước 1: Tạo SLSA Provenance trong Build Workflow

slsa-framework/slsa-github-generator của GitHub xử lý phần nặng nhọc. Đây là một workflow tối giản:

# .github/workflows/container-slsa.yml
name: Build and Attest Container

on:
  push:
    tags: ['v*']

jobs:
  build:
    permissions:
      contents: read
      packages: write
      id-token: write   # Bắt buộc để ký OIDC
    runs-on: ubuntu-latest
    outputs:
      image: ghcr.io/myorg/myapp
      digest: ${{ steps.build.outputs.digest }}

    steps:
      - uses: actions/checkout@v4

      - name: Build và push image
        id: build
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/myorg/myapp:${{ github.ref_name }}

  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      packages: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      image: ${{ needs.build.outputs.image }}
      digest: ${{ needs.build.outputs.digest }}
      registry-username: ${{ github.actor }}
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

Sau khi chạy, attestation sẽ nằm trong registry của bạn dưới dạng OCI artifact bên cạnh image — không cần database hay storage riêng. docker/build-push-action xuất digest của image trực tiếp dưới dạng steps.build.outputs.digest, nên không cần bước export thêm.

Bước 2: Xác minh Image trước khi Deploy

Cài Cosign CLI trên máy trạm hoặc trong CD pipeline của bạn:

# Cài cosign
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# Cài slsa-verifier
curl -LO https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64
chmod +x slsa-verifier-linux-amd64
sudo mv slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier

Sau đó xác minh provenance trước khi pull hoặc deploy:

# Xác minh SLSA provenance — xác nhận image được build từ repo của bạn
slsa-verifier verify-image \
  ghcr.io/myorg/myapp@sha256:abc123... \
  --source-uri github.com/myorg/myrepo \
  --source-tag v1.2.3

# Kết quả mong đợi:
# PASSED: Verified SLSA provenance
# slsa_level: 3

Luôn xác minh theo digest, không phải theo tag. Tag có thể thay đổi — digest thì không.

Bước 3: Ký Image bằng Cosign

SLSA provenance bảo đảm quá trình build. Ký bằng Cosign bảo đảm chính artifact image. Dùng cả hai:

# Ký không cần key (dùng GitHub OIDC — không cần quản lý key)
cosign sign --yes ghcr.io/myorg/myapp@sha256:abc123...

# Xác minh
cosign verify \
  --certificate-identity-regexp 'https://github.com/myorg/myrepo/.github/workflows/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/myorg/myapp@sha256:abc123...

Ký không cần key (qua Fulcio CA và Rekor transparency log của Sigstore) loại bỏ hoàn toàn nỗi đau quản lý private key. Identity đến từ OIDC token của GitHub Actions — bằng chứng mật mã tồn tại ngắn hạn, gắn với workflow run thực tế, không phải secret tồn tại lâu dài mà ai đó có thể vô tình để lộ hoặc commit lên repo.

Thực thi Xác minh tại Thời điểm Deploy

Ký image sẽ vô nghĩa nếu không có gì kiểm tra chữ ký trước khi chạy. Trong Kubernetes, policy admission controller là điểm thực thi của bạn. Đây là một Kyverno policy chặn image chưa được ký:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-image-signature
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-cosign-signature
      match:
        resources:
          kinds: [Pod]
      verifyImages:
        - imageReferences:
            - "ghcr.io/myorg/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/myrepo/.github/workflows/container-slsa.yml@refs/tags/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Bất kỳ pod nào cố chạy image chưa được ký hoặc đã bị can thiệp từ registry của bạn đều bị API server từ chối trước khi khởi động.

SLSA Level 3 Bổ sung Gì

Sử dụng generator_container_slsa3.yml đã đạt được chất lượng SLSA Level 3 provenance. Generator chạy trong môi trường độc lập mà build job chính của bạn không thể chạm vào hay chỉnh sửa. Các đặc tính then chốt ở L3:

  • Provenance được tạo trong môi trường mà build job không thể tác động.
  • Định nghĩa build (Dockerfile + workflow của bạn) được ghi lại trong provenance.
  • Ký diễn ra qua identity được hardware hỗ trợ (GitHub OIDC), không phải key do developer quản lý.

Mẹo Thực tế từ Triển khai Thực tế

  • Ghim base image theo digest. FROM ubuntu:22.04 là một mục tiêu di động. Dùng FROM ubuntu@sha256:abc... để provenance của bạn thực sự có ý nghĩa khi ai đó kiểm toán sau này.
  • Lưu provenance cùng với artifact. Các OCI registry hỗ trợ OCI 1.1 — GHCR, ECR, GCR — lưu attestation dưới dạng referrer. Không cần database riêng.
  • Tích hợp xác minh vào CD pipeline, không chỉ máy tính của developer. Một bước thủ công mà ai đó có thể bỏ qua không phải là biện pháp kiểm soát thực sự.
  • Ghi log tất cả sự kiện xác minh. Khi xác minh chữ ký thất bại, bạn muốn điều đó xuất hiện trong SIEM. Cosign và slsa-verifier đều trả về exit code khác 0 khi thất bại, nên việc bọc chúng trong các bước pipeline khá đơn giản.
  • Đừng bỏ qua việc tạo SBOM. SLSA provenance cho bạn biết image được build như thế nào; SBOM cho bạn biết nó chứa gì. Dùng syft hoặc hỗ trợ SBOM tích hợp sẵn của docker buildx cùng với SLSA attestation.

Những Sai lầm Thường gặp cần Tránh

Các team thường vấp ngã ở đây: triển khai ký nhưng bỏ qua hoàn toàn việc xác minh. Ký là phần dễ. Giá trị chỉ xuất hiện khi bạn thực sự kiểm tra chữ ký tại ranh giới thực thi — admission controller, CD gate, policy engine.

Tag image có thể thay đổi trong policy xác minh là cái bẫy thứ hai. Một policy xác minh myapp:latest có thể được thỏa mãn bởi bất kỳ image được ký nào được push dưới dạng latest — đó không phải là bảo vệ chống tag hijacking. Luôn tham chiếu digest trong deployment production.

Cuối cùng, đừng coi SLSA như một hộp kiểm tuân thủ. Level 1 provenance không có thực thi gần như vô nghĩa. Mục tiêu là làm cho nó có thể phát hiện được một cách có xác minh khi một artifact bị can thiệp cố gắng lên production — không phải để đánh dấu vào một ô trong bảng câu hỏi bảo mật.

Bước tiếp theo

Sau khi đã thiết lập SLSA Level 2–3 cho container, có một số mở rộng đáng thực hiện:

  • Mở rộng pipeline tương tự cho artifact không phải container — Helm chart, binary, Lambda ZIP — sử dụng generic artifact workflow của slsa-github-generator.
  • Thiết lập giám sát Rekor log để cảnh báo về các sự kiện ký không mong đợi cho identity của bạn.
  • Kiểm tra cấp độ SLSA của các dependency. Công cụ như deps.dev hiện đã hiển thị dữ liệu SLSA provenance cho các package mã nguồn mở.

Bảo mật chuỗi cung ứng không có đích đến cuối cùng. Mỗi lớp bạn tăng cường là một điểm vào ít hơn cho SolarWinds tiếp theo. Tích hợp SLSA provenance vào container pipeline của bạn là một trong những cải tiến cụ thể và có thể xác minh nhất mà bạn có thể thực hiện ngay bây giờ.

Share: