Skip to content

Attestation: Cơ Chế Ký Số Mật Mã và Artifact Analysis

Tại sao quan trọng trong production

Attestation là trái tim của Binary Authorization. Nhưng trong thực tế production, người ta thường chỉ biết "tạo attestation bằng gcloud rồi deploy" mà không hiểu:

  • Attestation thực sự là gì về mặt kỹ thuật (một JSON document với chữ ký mật mã)
  • Signature binding — attestation ký trên cái gì và tại sao phải là digest chứ không phải tag
  • Verification path — tại deploy time, enforcer tìm attestation ở đâu và verify như thế nào
  • Failure modes — tại sao attestation có thể fail dù được tạo đúng quy trình

Hiểu cơ chế này giúp debug tại sao policy reject một image dù CI đã "sign" nó, và thiết kế pipeline đúng từ đầu.


Internal Model: Anatomy của Một Attestation

Attestation Là Một Artifact Analysis Occurrence

Về mặt kỹ thuật, attestation là một occurrence trong Artifact Analysis (Cloud Container Analysis API). Artifact Analysis dùng mô hình note/occurrence:

Note: "loại" metadata (định nghĩa một attestor)
  └── Occurrence: instance cụ thể (attestation cho một image cụ thể)

Một occurrence cho attestation có structure:

json
{
  "resourceUri": "https://gcr.io/my-project/my-app@sha256:abc123...",
  "noteName": "projects/attestor-project/notes/qa-approval-note",
  "kind": "ATTESTATION",
  "attestation": {
    "serializedPayload": "<base64-encoded-payload>",
    "signatures": [
      {
        "signature": "<base64-encoded-signature>",
        "publicKeyId": "projects/.../cryptoKeyVersions/1"
      }
    ]
  }
}

Giải mã từng trường:

resourceUri: URI của image, bắt buộc phải dùng digest (@sha256:...), không thể dùng tag. Đây là key để Binary Authorization lookup attestation — nó query bằng digest của image đang được deploy.

noteName: Trỏ đến Artifact Analysis note của attestor tương ứng. Binary Authorization biết note nào để query dựa vào cấu hình của attestor trong policy.

serializedPayload: Payload được ký, encode base64. Khi decode, đây là một JSON document theo format Binary Authorization.

signatures: Mảng chữ ký. Mỗi signature là chữ ký mật mã của serializedPayload và ID của public key tương ứng.

Payload Format: Cái Gì Được Ký

Payload (trước khi base64 encode) có format:

json
{
  "critical": {
    "type": "Google cloud binauthz container signature",
    "image": {
      "docker-manifest-digest": "sha256:abc123def456..."
    },
    "identity": {
      "docker-reference": "gcr.io/my-project/my-app"
    }
  }
}

docker-manifest-digest: SHA256 digest của image manifest. Đây là định danh bất biến của image — không thể bị thay thế mà không thay đổi digest.

docker-reference: Registry path của image (không có digest). Đây là context — biết image này đến từ registry nào.

Tại sao thiết kế như vậy? Payload binding cả digest (để uniquely identify image content) lẫn registry path (để context). Nếu chỉ ký digest, một image từ untrusted registry có thể có cùng digest với image trusted (collision attack, dù cực kỳ khó trong thực tế). Kết hợp registry path + digest tạo ra binding mạnh hơn.


Cryptographic Signing: Cơ Chế Bên Trong

PKIX Signing với Cloud KMS

Đây là flow signing khi dùng Cloud KMS (khuyến nghị cho production):

1. Tạo payload JSON
2. Base64-encode payload
3. Gọi Cloud KMS API để ký:
   - Input: base64-encoded payload (hoặc hash của nó)
   - KMS signing key: ECDSA P-256
   - Output: ECDSA signature bytes
4. Base64-encode signature
5. Tạo Artifact Analysis occurrence với payload + signature

Code flow khi dùng gcloud (underlying steps):

bash
# Tạo payload
PAYLOAD_PATH=/tmp/generated_payload.json
IMAGE_DIGEST="sha256:abc123..."
IMAGE_PATH="gcr.io/my-project/my-app"

cat > ${PAYLOAD_PATH} << EOF
{
  "critical": {
    "type": "Google cloud binauthz container signature",
    "image": {
      "docker-manifest-digest": "${IMAGE_DIGEST}"
    },
    "identity": {
      "docker-reference": "${IMAGE_PATH}"
    }
  }
}
EOF

# Sign payload với Cloud KMS
gcloud kms asymmetric-sign \
  --location=us-central1 \
  --keyring=binauthz-keyring \
  --key=signing-key \
  --version=1 \
  --digest-algorithm=sha256 \
  --input-file=${PAYLOAD_PATH} \
  --signature-file=/tmp/signature.bin

# Tạo attestation
gcloud container binauthz attestations create \
  --project=${ATTESTATION_PROJECT} \
  --artifact-url="${IMAGE_PATH}@${IMAGE_DIGEST}" \
  --attestor=projects/${ATTESTOR_PROJECT}/attestors/${ATTESTOR_ID} \
  --signature-file=/tmp/signature.bin \
  --public-key-id="${KEY_URI}"

Key ID trong occurrence: publicKeyId trong occurrence phải khớp với ID của một public key được đăng ký trong attestor. Binary Authorization dùng publicKeyId để biết dùng public key nào để verify. Nếu không khớp → verification fail ngay cả khi signature về mặt mật mã là valid.

PKIX vs PGP: Sự Khác Biệt Bản Chất

PKIX (X.509):

  • Key được identify bằng X.509 certificate hoặc raw public key PEM
  • Tích hợp native với Cloud KMS
  • Public key ID là projects/.../cryptoKeyVersions/N
  • Là lựa chọn khuyến nghị cho mọi implementation mới

PGP:

  • Dùng fingerprint của PGP key làm ID
  • Không tích hợp với Cloud KMS — key phải lưu locally hoặc trong secret manager
  • Legacy format, Binary Authorization vẫn support nhưng không phát triển thêm
  • Không phù hợp cho automated CI/CD vì cần PGP key management riêng

Tại sao PKIX được ưu tiên? Cloud KMS cung cấp:

  1. Hardware-backed key storage (HSM option)
  2. Audit logging cho mọi signing operation
  3. Key rotation và versioning
  4. IAM control ai có thể dùng key để sign
  5. Không cần expose private key ra ngoài Google infrastructure

Artifact Analysis: Storage Layer Cho Attestation

Note/Occurrence Model: Tại Sao Artifact Analysis?

Artifact Analysis (Container Analysis) ban đầu được thiết kế để lưu vulnerability findings. Binary Authorization tái sử dụng infrastructure này vì:

  1. Immutable storage: Occurrences không thể thay đổi sau khi tạo (chỉ có thể tạo mới hoặc xóa)
  2. Content-addressable lookup: Query theo image digest, không theo tag
  3. IAM integration: Ai có thể đọc/ghi occurrences được kiểm soát bởi IAM
  4. VPC Service Controls: Có thể đưa vào perimeter để chống exfiltration
  5. Audit trail: Mọi thao tác đều được log trong Cloud Audit Logs

Cấu Trúc Lưu Trữ và Query Path

Khi Binary Authorization enforcer cần verify attestation cho một image:

1. Enforcer nhận image digest từ Pod spec
2. Query Artifact Analysis:
   GET /v1/projects/{ATTESTATION_PROJECT}/occurrences?filter=
       resourceUrl="https://{IMAGE_PATH}@sha256:{DIGEST}"
       AND noteProjectId="{NOTE_PROJECT}"
       AND noteId="{NOTE_ID}"
       AND kind="ATTESTATION"
3. Nhận danh sách occurrences
4. Với mỗi occurrence:
   - Lấy signature và publicKeyId
   - Tìm public key trong attestor matching publicKeyId
   - Verify: ECDSA.verify(payload, signature, publicKey)
5. Nếu ít nhất một signature verify thành công → attestor này satisfied
6. Lặp lại cho tất cả attestors trong policy rule

Tại sao query theo note? Mỗi attestor gắn với đúng một note. Note là "category" của attestation. Khi query, Binary Authorization lọc chỉ occurrences thuộc note của attestor đang được verify — tránh nhầm lẫn attestation từ attestor khác.

Cross-Project Attestation Lookup

Trong multi-project setup:

Deployment Project → Policy → Attestors (Attestor Project)
                                    ↓ (note reference)
                                Artifact Analysis (Note Project)
                                    ↓ (occurrence)
                                Attestations (Attestation Project)

Binary Authorization service account cần có permission để:

  • Đọc attestors từ attestor project
  • Đọc notes từ note project
  • Đọc occurrences từ attestation project

IAM roles:

bash
# Grant Binary Authorization service account read access
BINAUTHZ_SA="service-${PROJECT_NUMBER}@gcp-sa-binaryauthorization.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding ${ATTESTATION_PROJECT} \
  --member="serviceAccount:${BINAUTHZ_SA}" \
  --role="roles/containeranalysis.occurrences.viewer"

gcloud projects add-iam-policy-binding ${ATTESTOR_PROJECT} \
  --member="serviceAccount:${BINAUTHZ_SA}" \
  --role="roles/binaryauthorization.attestorsViewer"

Cloud Build Integration: Automated Attestation

Cloud Build Provenance

Cloud Build tự động tạo build provenance — một dạng attestation đặc biệt được tạo bởi infrastructure Cloud Build, không thể bị giả mạo bởi user code trong build pipeline.

Cloud Build provenance chứa:

  • Source code repository và commit SHA
  • Build configuration (cloudbuild.yaml hoặc trigger config)
  • Build environment (Cloud Build worker)
  • Output artifacts (image digests)
  • Timestamps

Khác với user-created attestation, Cloud Build provenance được ký bởi Google — không phải bởi user's key. Điều này có nghĩa:

yaml
# Attestor cho Cloud Build provenance không cần user key
# Đây là attestor đặc biệt "built-by-cloud-build"
gcloud container binauthz attestors create built-by-cloud-build \
  --attestation-authority-note=projects/${PROJECT_ID}/notes/built-by-cloud-build

Binary Authorization có thể verify không chỉ "image được build" mà cả "image được build từ source code ở đây, với config này, trong Cloud Build environment" — không thể giả mạo bằng cách submit image từ local.

SLSA Provenance

SLSA (Supply chain Levels for Software Artifacts) là framework cao hơn. Cloud Build tạo SLSA provenance theo SLSA level 1 (và hướng đến level 3). Continuous Validation có thể verify SLSA provenance qua SLSA check (xem Chương 34.04).

SLSA provenance structure (ví dụ):

json
{
  "builder": {
    "id": "https://cloudbuild.googleapis.com/GoogleHostedWorker@v0.2"
  },
  "buildType": "https://cloudbuild.googleapis.com/CloudBuildYaml@v0.1",
  "invocation": {
    "configSource": {
      "uri": "https://github.com/my-org/my-repo",
      "digest": {"sha1": "commitSHA"},
      "entryPoint": "cloudbuild.yaml"
    }
  },
  "metadata": {
    "buildStartedOn": "2024-01-15T10:00:00Z",
    "buildFinishedOn": "2024-01-15T10:05:00Z"
  },
  "materials": [
    {
      "uri": "https://github.com/my-org/my-repo",
      "digest": {"sha1": "commitSHA"}
    }
  ]
}

Constraints, Trade-offs và Failure Modes

Timing Issue: Attestation Propagation Lag

Artifact Analysis có eventual consistency. Sau khi tạo occurrence, có thể mất từ vài giây đến 1-2 phút để Binary Authorization enforcer thấy nó. Nếu CI pipeline tạo attestation xong ngay lập tức trigger deploy, có thể xảy ra:

T=0: CI tạo attestation occurrence
T=1: CI trigger kubectl apply
T=2: Binary Authorization enforcer query Artifact Analysis
     → Occurrence chưa visible
     → Deploy bị BLOCK dù attestation hợp lệ

Giải pháp: Thêm sleep 30 hoặc implement retry với exponential backoff trong CI pipeline sau khi tạo attestation. Alternative: dùng gcloud container binauthz attestations list để verify attestation visible trước khi deploy.

Attestation Scope Confusion: Tag vs Digest

Đây là lỗi cực kỳ phổ biến. Attestation gắn với digest của image, không phải tag. Nhưng khi deploy, nếu Pod spec dùng tag (image: gcr.io/my-project/app:v1.2.3), có hai vấn đề:

Vấn đề 1: Binary Authorization phải resolve tag sang digest Binary Authorization phải gọi Container Registry/Artifact Registry API để resolve tag → digest trước khi lookup attestation. Nếu registry không accessible hoặc tag bị move, lookup fail.

Vấn đề 2: Tag là mutable Giữa thời điểm CI tạo attestation (với digest A) và thời điểm deploy (resolve tag → digest), tag có thể đã bị repoint sang image khác (digest B). Binary Authorization có thể allow deploy digest B vì nó không kiểm tra lại attestation sau khi resolve — nó dùng digest vừa resolve để lookup.

Mental model đúng: Attestation là về content (digest), không phải label (tag). Một tag là một con trỏ mutable. Attaching trust to a mutable pointer là security anti-pattern.

Giải pháp duy nhất thực sự an toàn: Pin digest trong manifest:

yaml
containers:
- name: app
  # KHÔNG an toàn:
  image: gcr.io/my-project/app:v1.2.3
  # AN TOÀN:
  image: gcr.io/my-project/app@sha256:abc123def456789...

Key Rotation: Attestations Cũ Và Key Mới

Khi rotate signing key, attestations cũ (được ký bằng key cũ) vẫn cần verify được. Nếu xóa public key cũ khỏi attestor, tất cả deployments của image cũ sẽ fail.

Pattern đúng cho key rotation:

  1. Tạo key version mới (Cloud KMS tự increment version)
  2. Thêm public key của version mới vào attestor (attestor có thể có nhiều public keys)
  3. Bắt đầu ký attestation mới với key version mới
  4. Để key cũ còn trong attestor cho đến khi tất cả images được ký với key cũ đã được re-signed hoặc không còn trong production
  5. Sau đó disable/remove key cũ
bash
# Thêm key version mới mà không xóa cũ
gcloud container binauthz attestors public-keys add \
  --attestor=${ATTESTOR_ID} \
  --keyversion="projects/${PROJECT_ID}/locations/us-central1/keyRings/binauthz-keyring/cryptoKeys/signing-key/cryptoKeyVersions/2"

Attestation Replay Attack

Một attestation đã tạo cho image X có thể bị "replay" nếu attacker tìm cách tạo image X với digest giống hệt nhưng ở registry khác. Trên thực tế cực kỳ khó (SHA-256 collision resistance), nhưng cần hiểu:

  • Attestation bind với docker-manifest-digest + docker-reference (registry path)
  • Nếu policy allowlist quá rộng (wildcard registry), image từ registry khác với cùng digest có thể pass

Phòng ngừa: Dùng Artifact Registry (không phải Docker Hub hay registry external) và cấu hình allowlist chặt chẽ.


GCP-native Implementation: Tạo Attestation Trong CI/CD

Với Cloud Build

yaml
# cloudbuild.yaml
steps:
# Bước 1: Build image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-app:$SHORT_SHA', '.']

# Bước 2: Push image
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/$PROJECT_ID/my-app:$SHORT_SHA']

# Bước 3: Lấy digest
- name: 'gcr.io/cloud-builders/docker'
  entrypoint: 'bash'
  args:
  - '-c'
  - |
    docker inspect --format='{{index .RepoDigests 0}}' \
      gcr.io/$PROJECT_ID/my-app:$SHORT_SHA \
      | cut -d@ -f2 > /workspace/image-digest.txt

# Bước 4: Tạo attestation
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: 'bash'
  args:
  - '-c'
  - |
    IMAGE_DIGEST=$(cat /workspace/image-digest.txt)
    IMAGE_PATH="gcr.io/$PROJECT_ID/my-app"
    
    gcloud container binauthz attestations sign-and-create \
      --artifact-url="${IMAGE_PATH}@${IMAGE_DIGEST}" \
      --attestor="projects/$PROJECT_ID/attestors/qa-attestor" \
      --keyversion="projects/$PROJECT_ID/locations/us-central1/keyRings/binauthz-keyring/cryptoKeys/signing-key/cryptoKeyVersions/1"
    
    # Wait for propagation
    sleep 30

Lưu ý sign-and-create — đây là gcloud command tổng hợp thực hiện cả signing và tạo occurrence trong một bước.

Verify Attestation Tồn Tại

bash
# Kiểm tra attestation có được tạo đúng không
IMAGE_WITH_DIGEST="gcr.io/my-project/my-app@sha256:abc123..."

gcloud container binauthz attestations list \
  --artifact-url="${IMAGE_WITH_DIGEST}" \
  --attestor=qa-attestor \
  --attestor-project=my-attestor-project

# Output kỳ vọng: dòng listing occurrence ID, creation time
# Nếu empty → attestation chưa được tạo hoặc chưa propagate

Tài liệu tham khảo