Skip to content

Enforcement Path: Từ kubectl apply Đến Admission Decision

Tại sao quan trọng trong production

Binary Authorization enforcement không phải là một firewall độc lập đứng ngoài cluster. Nó là một ValidatingAdmissionWebhook cắm vào Kubernetes admission pipeline, với tất cả implication về failure modes và behavior của webhook. Hiểu đường đi đầy đủ của request — từ lúc kubectl apply gửi manifest đến lúc Pod được tạo hoặc bị chặn — là điều kiện tiên quyết để debug, thiết kế pipeline đúng, và không bị bất ngờ trong production incidents.

Đặc biệt quan trọng: image digest pinning là vấn đề bảo mật thực sự, không chỉ là best practice. Dùng tag thay vì digest tạo ra một khoảng trống mà Binary Authorization không thể đóng được — không phải vì lỗi config, mà vì bản chất của tag là mutable pointer.


Internal Model: Binary Authorization Như Một ValidatingAdmissionWebhook

Vị Trí Trong Admission Pipeline

Binary Authorization tích hợp vào GKE dưới dạng một ValidatingAdmissionWebhook được Google quản lý. Đây không phải webhook do người dùng deploy — nó được GKE provisioning tự động khi bật Binary Authorization trên cluster.

Vị trí của nó trong admission pipeline:

kubectl apply


Kubernetes API Server

    ├─ Authentication
    ├─ Authorization (RBAC)

    ├─ Mutating Admission Webhooks (user webhooks)
    │     ↓
    ├─ Object schema validation

    ├─ Validating Admission Webhooks
    │     ├─ User ValidatingAdmissionWebhooks
    │     └─ Binary Authorization webhook ← ĐÂY


etcd (object stored)


Pod scheduled & running

Binary Authorization webhook chỉ validate — không thể mutate request. Điều này quan trọng: nó không thể thêm annotation, thay đổi image, hay patch manifest. Nó chỉ quyết định "allow" hay "deny".

Cấu Hình Webhook Trên GKE

Khi bật Binary Authorization (--binauthz-evaluation-mode), GKE tự tạo một ValidatingWebhookConfiguration trong cluster:

yaml
# Tự động được tạo bởi GKE, không sửa thủ công
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: imagepolicywebhook.image-policy.k8s.io
webhooks:
- name: imagepolicywebhook.image-policy.k8s.io
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  - operations: ["CREATE"]
    apiGroups: ["apps"]
    apiVersions: ["v1"]
    resources: ["deployments", "replicasets", "daemonsets", "statefulsets"]
  failurePolicy: Fail
  sideEffects: None
  timeoutSeconds: 10

failurePolicy: Fail: Nếu webhook call thất bại (timeout, connection refused, internal error), admission request bị deny. Đây là fail-safe behavior — bảo vệ cluster khi Binary Authorization API gặp sự cố, nhưng đồng nghĩa deployments bị chặn cho đến khi service phục hồi (hoặc dùng break-glass — xem file 05).

Lưu ý về scope: Webhook trigger cho pods, deployments, replicasets v.v. khi operation là CREATE. Update không trigger webhook cho container images (chỉ trigger khi Pod spec thay đổi và tạo Pod mới). Đây là lý do continuous validation tồn tại — deployment-time enforcement không bắt được trường hợp policy thay đổi sau khi Pod đã chạy.


Đường Đi Đầy Đủ: Bước Từng Bước

Phase 1: Request Đến Webhook

Khi kubectl apply -f deployment.yaml được thực thi:

  1. kubectl serialize manifest → gửi HTTP request đến Kubernetes API server

  2. API server nhận request, authenticate (verify token/certificate), authorize (RBAC check)

  3. Mutating webhooks chạy trước — nếu có webhook nào inject sidecar hay thêm annotation vào Pod spec, nó xảy ra ở đây

  4. Validating webhooks chạy — Binary Authorization webhook nhận AdmissionReview request:

json
{
  "kind": "AdmissionReview",
  "request": {
    "uid": "unique-id",
    "kind": {"group": "apps", "version": "v1", "kind": "Deployment"},
    "namespace": "production",
    "operation": "CREATE",
    "object": {
      "spec": {
        "template": {
          "spec": {
            "containers": [
              {
                "name": "app",
                "image": "gcr.io/my-project/my-app@sha256:abc123..."
              }
            ]
          }
        }
      }
    }
  }
}

Phase 2: Image Extraction và Digest Resolution

Binary Authorization webhook extract tất cả container images từ Pod spec, bao gồm:

  • containers[]
  • initContainers[]
  • ephemeralContainers[]

Với mỗi image reference, webhook kiểm tra:

  • Image có dùng digest? (@sha256:...) → Dùng trực tiếp
  • Image dùng tag? → Phải resolve tag sang digest qua Container Registry/Artifact Registry API

Vấn đề với tag resolution (CRITICAL):

Khi image dùng tag, Binary Authorization phải gọi registry API để resolve. Đây là một network call. Nếu:

  • Registry không accessible → resolution fail → admission fail (do failurePolicy: Fail)
  • Tag đã bị moved sang image khác sau khi CI tạo attestation → digest mới không có attestation → deploy bị block dù CI pipeline "success"

Theo Google Cloud documentation: "you must deploy the image using the digest rather than a tag." Đây không phải suggestion — là hard requirement cho bảo mật thực sự.

Phase 3: Allowlist Check

Trước khi làm attestation lookup, Binary Authorization kiểm tra image có nằm trong allowlist không:

yaml
admissionWhitelistPatterns:
- namePattern: gcr.io/google_containers/*          # GKE system images
- namePattern: k8s.gcr.io/**                        # Kubernetes system
- namePattern: gcr.io/my-project/internal-tools/**  # Internal tools exempt

Nếu image match bất kỳ pattern nào → tự động allow, bỏ qua tất cả attestation check. Đây là lý do cần thận trọng với allowlist — mỗi entry là một exception to policy enforcement.

globalPolicyEvaluationMode: ENABLE thêm một layer allowlist nữa: danh sách system images do Google quản lý (GKE node agent images, system components). Danh sách này được cập nhật tự động và không cần user maintain.

Phase 4: Rule Matching

Binary Authorization xác định rule nào áp dụng cho deployment này:

Input: cluster identity = "us-central1-a.production-cluster"

Lookup order:
1. Có cluster-specific rule không?     → "us-central1-a.production-cluster"
2. Không có? → dùng defaultAdmissionRule

Cluster identity format: {location}.{cluster-name} — ví dụ us-central1-a.my-prod-cluster. GKE cluster report identity này khi call Binary Authorization API. Nếu cluster name trong rule không khớp chính xác (case-sensitive), rule không được áp dụng và system fallback về default rule — một nguồn gốc của misconfiguration thầm lặng.

Phase 5: Policy Evaluation

Tùy evaluationMode của rule được chọn:

ALWAYS_ALLOW: Return allowed: true ngay lập tức.

ALWAYS_DENY: Return allowed: false ngay lập tức (trừ khi image trong allowlist).

REQUIRE_ATTESTATION: Trigger attestation lookup cho mỗi attestor trong danh sách requireAttestationsBy:

For each attestor A in rule.requireAttestationsBy:
    attestor_resource = get_attestor(A)
    note = attestor_resource.userOwnedDrydockNote

    occurrences = artifact_analysis.list_occurrences(
        filter = 'resourceUrl="{image_with_digest}"
                  AND noteProjectId="{note.projectId}"
                  AND noteId="{note.noteId}"
                  AND kind="ATTESTATION"'
    )

    valid_signature_found = false
    for occ in occurrences:
        for sig in occ.attestation.signatures:
            pubkey = find_pubkey(attestor_resource, sig.publicKeyId)
            if pubkey and verify_signature(sig, payload, pubkey):
                valid_signature_found = true
                break

    if not valid_signature_found:
        return DENY  # attestor A không được thỏa mãn

return ALLOW  # tất cả attestors satisfied

AND semantics: Mọi attestor trong danh sách phải được thỏa mãn. Nếu policy yêu cầu 3 attestors nhưng image chỉ có attestation của 2 → DENY. Không có cách nào "partial allow".

Điều này cho phép xây dựng multi-stage approval: ví dụ image phải có attestation từ security-scan-attestor (CVE scan pass) VÀ qa-attestor (QA approval) VÀ built-by-cloud-build (origin verified).

Phase 6: Enforcement Action

Kết quả evaluation → enforcement action tùy enforcementMode:

ENFORCED_BLOCK_AND_AUDIT_LOG:

  • DENY: Return AdmissionReview response với allowed: false và reason
  • AUDIT: Write event vào Cloud Audit Logs
json
{
  "response": {
    "uid": "unique-id",
    "allowed": false,
    "status": {
      "message": "Image gcr.io/my-project/my-app@sha256:abc123 denied by attestor projects/my-project/attestors/qa-approval: No attestations found"
    }
  }
}

Kubernetes API server trả lỗi về kubectl, controller không tạo được Pods.

DRYRUN_AUDIT_LOG_ONLY:

  • Bất kể evaluation kết quả gì: Return allowed: true
  • AUDIT: Write policy violation vào Cloud Audit Logs (nếu evaluation là DENY)

Đây là dry-run mode — image được deploy dù không thỏa mãn policy, nhưng violation được ghi lại cho phân tích.

Kết quả visible từ kubectl

Khi deployment bị block:

bash
$ kubectl apply -f deployment.yaml
Error from server: error when creating "deployment.yaml":
  admission webhook "imagepolicywebhook.image-policy.k8s.io" denied the request:
  Image gcr.io/my-project/my-app@sha256:abc123 denied by attestor
  projects/my-project/attestors/qa-approval: No attestations found

Image Digest Pinning: Security Model Đầy Đủ

Tại Sao Tag Là Mutable Pointer Nguy Hiểm

Container image tag là một alias — một con trỏ trong registry trỏ đến một image manifest. Tag có thể bị cập nhật bất kỳ lúc nào bởi bất kỳ ai có quyền push. Điều này tạo ra attack surface:

Scenario: Tag swap sau khi attestation

T=0: CI build image:v1.2.3 → digest SHA256:GOOD_CONTENT
T=1: CI tạo attestation cho SHA256:GOOD_CONTENT
T=2: Attacker có quyền push → push malicious image → image:v1.2.3 → SHA256:BAD_CONTENT
T=3: Deploy với: image: gcr.io/project/app:v1.2.3
T=4: BinAuthz resolve v1.2.3 → SHA256:BAD_CONTENT
T=5: BinAuthz lookup attestation cho SHA256:BAD_CONTENT → NOT FOUND → DENY ✓

Trong scenario này Binary Authorization ngăn được vì BAD_CONTENT không có attestation. Nhưng:

T=6: Attacker cũng có quyền tạo attestation
T=7: Attacker tạo attestation cho SHA256:BAD_CONTENT
T=8: BinAuthz lookup SHA256:BAD_CONTENT → FOUND → ALLOW ✗

Đây chính xác là lý do tại sao separation of duties quan trọng hơn bất kỳ thứ gì: developer không được có quyền tạo attestation cho code của chính họ. Attestation phải đến từ trusted CI/CD pipeline với service account được kiểm soát chặt chẽ.

Scenario: Race condition không có attacker

T=0: CI build và tạo attestation cho image:v1.2.3 (digest: SHA256:A)
T=1: Developer khác push hotfix → image:v1.2.3 → SHA256:B (không có attestation)
T=2: CD pipeline deploy: image: gcr.io/project/app:v1.2.3
T=3: Registry resolve v1.2.3 → SHA256:B
T=4: BinAuthz lookup SHA256:B → No attestation → DENY
→ Deploy fail dù CI "success"

Đây là confusing failure mode trong production: CI log show "attestation created successfully" nhưng deploy fail với "no attestations found". Debug khó vì phải track digest nào được sign và digest nào đang được deploy.

Digest là Content-Addressable Identifier

SHA-256 digest (sha256:{64-hex-chars}) là hash của image manifest JSON. Manifest bao gồm:

  • Layer digests
  • Config digest (image config)
  • Media type
  • Labels và metadata

Thay đổi bất kỳ byte nào trong image → manifest thay đổi → digest thay đổi. Hai image có cùng digest phải có cùng content (trừ SHA-256 collision, cực kỳ khó trong thực tế).

Khi dùng digest:

yaml
image: gcr.io/my-project/my-app@sha256:abc123def456789...

Binary Authorization không cần gọi registry để resolve — nó dùng trực tiếp digest này để lookup attestation. Không có ambiguity, không có race condition, không có mutable pointer issue.

Lấy Digest Trong CI/CD

Sau khi push image, lấy digest:

bash
# Cách 1: gcloud artifacts (reliable nhất)
IMAGE_DIGEST=$(gcloud artifacts docker images describe \
  gcr.io/my-project/my-app:v1.2.3 \
  --format='get(image_summary.digest)')

# Cách 2: Docker inspect (cần image pulled locally)
IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \
  gcr.io/my-project/my-app:v1.2.3 | cut -d@ -f2)

# Cách 3: Crane (container registry CLI tool)
IMAGE_DIGEST=$(crane digest gcr.io/my-project/my-app:v1.2.3)

echo "Digest: ${IMAGE_DIGEST}"
# Output: sha256:abc123def456789...

Trong Kubernetes manifest:

yaml
spec:
  containers:
  - name: app
    # Tag: không đảm bảo bảo mật
    # image: gcr.io/my-project/my-app:v1.2.3
    # Digest: bất biến, an toàn
    image: gcr.io/my-project/my-app@sha256:abc123def456789...

Cloud Build Integration: Automated Attestation trong CI/CD

Tại Sao Automated Attestation Cần Thiết

Trong môi trường production, yêu cầu developer thủ công tạo attestation cho mỗi image không scalable và không secure. Automated attestation thông qua CI/CD pipeline đảm bảo:

  1. Consistency: Mọi image đều được xử lý theo cùng một quy trình
  2. Audit trail: Mọi attestation đều có build log tương ứng
  3. Non-repudiation: Attestation được ký bởi key do CI system kiểm soát
  4. Separation of duties: Developer không thể tự ký attestation cho code của mình

Cloud Build Workflow

yaml
# cloudbuild.yaml — pipeline hoàn chỉnh với Binary Authorization
steps:
# Step 1: Build image
- name: 'gcr.io/cloud-builders/docker'
  id: 'build-image'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA', '.']

# Step 2: Run tests
- name: 'gcr.io/cloud-builders/docker'
  id: 'run-tests'
  waitFor: ['build-image']
  args: ['run', '--rm', 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA', 'npm', 'test']

# Step 3: Push to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
  id: 'push-image'
  waitFor: ['run-tests']
  args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA']

# Step 4: Tạo attestation với digest
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  id: 'create-attestation'
  waitFor: ['push-image']
  entrypoint: 'bash'
  args:
  - '-c'
  - |
    set -euo pipefail
    IMAGE_PATH="gcr.io/$PROJECT_ID/$REPO_NAME"
    IMAGE_DIGEST=$(gcloud artifacts docker images describe \
      "${IMAGE_PATH}:$COMMIT_SHA" \
      --format='get(image_summary.digest)')

    echo "Signing image: ${IMAGE_PATH}@${IMAGE_DIGEST}"

    gcloud container binauthz attestations sign-and-create \
      --artifact-url="${IMAGE_PATH}@${IMAGE_DIGEST}" \
      --attestor="projects/$PROJECT_ID/attestors/cloud-build-attestor" \
      --keyversion="projects/$PROJECT_ID/locations/global/keyRings/binauthz-keyring/cryptoKeys/signing-key/cryptoKeyVersions/1"

    echo "${IMAGE_PATH}@${IMAGE_DIGEST}" > /workspace/image-with-digest.txt
    sleep 30  # Wait for Artifact Analysis propagation

# Step 5: Deploy với digest (không phải tag)
- name: 'gcr.io/cloud-builders/kubectl'
  id: 'deploy'
  waitFor: ['create-attestation']
  entrypoint: 'bash'
  args:
  - '-c'
  - |
    IMAGE_WITH_DIGEST=$(cat /workspace/image-with-digest.txt)
    kubectl set image deployment/my-app app="${IMAGE_WITH_DIGEST}" \
      --namespace=production

serviceAccount: 'cloud-build-sa@$PROJECT_ID.iam.gserviceaccount.com'

IAM cho Cloud Build service account:

bash
CLOUD_BUILD_SA="cloud-build-sa@${PROJECT_ID}.iam.gserviceaccount.com"

# Quyền đọc attestor
gcloud projects add-iam-policy-binding ${ATTESTOR_PROJECT} \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/binaryauthorization.attestorsViewer"

# Quyền ký bằng Cloud KMS key
gcloud kms keys versions add-iam-policy-binding 1 \
  --key=signing-key --keyring=binauthz-keyring --location=global \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/cloudkms.signerVerifier"

# Quyền tạo attestation occurrence
gcloud projects add-iam-policy-binding ${ATTESTATION_PROJECT} \
  --member="serviceAccount:${CLOUD_BUILD_SA}" \
  --role="roles/containeranalysis.occurrences.editor"

Built-By-Cloud-Build Attestor

Google cung cấp một attestor đặc biệt — built-by-cloud-build — xác minh rằng image được build bởi Cloud Build infrastructure (không phải developer thủ công push). Attestor này verify Cloud Build provenance, được ký bởi Google — không thể giả mạo bởi user code trong build pipeline.

Khía cạnhUser-created attestationCloud Build provenance
Được ký bởiUser's KMS keyGoogle (infrastructure-level)
Yêu cầu configCần thêm build stepTự động
Nội dungCustomSource, config, build env
Trust levelTrust theo key securityTrust theo Cloud Build infra

SLSA Provenance

SLSA (Supply chain Levels for Software Artifacts) là framework chuẩn hóa cho supply chain security. Cloud Build tạo SLSA provenance tự động, kèm với mỗi build, chứa:

  • builder.id: Định danh builder (https://cloudbuild.googleapis.com/GoogleHostedWorker)
  • invocation.configSource: Source code repository + commit SHA
  • materials: Input artifacts
  • Timestamps và build metadata

SLSA provenance lưu trong Artifact Analysis và có thể được verify bởi Binary Authorization Continuous Validation qua SLSA check type (xem file 04.continuous-validation.md).


Audit Logging: Visibility Vào Enforcement

Mọi Binary Authorization enforcement event ghi vào Cloud Audit Logs (Admin Activity log). Cấu trúc key:

json
{
  "protoPayload": {
    "methodName": "io.k8s.core.v1.pods.create",
    "metadata": {
      "binaryAuthorization": {
        "policyRef": "projects/my-project/policy",
        "containerChecks": [
          {
            "containerName": "app",
            "image": "gcr.io/my-project/my-app@sha256:abc123",
            "verdict": "VERDICT_DENY",
            "checkResults": [
              {
                "verdict": "VERDICT_DENY",
                "explanation": "No attestations found that were valid and signed by a key in [projects/my-project/attestors/qa-approval]"
              }
            ]
          }
        ]
      }
    }
  }
}

Query để monitor violations:

bash
gcloud logging read \
  'resource.type="k8s_cluster"
   AND protoPayload.metadata.binaryAuthorization.containerChecks.verdict="VERDICT_DENY"' \
  --project=${PROJECT_ID} \
  --format='table(timestamp,
    protoPayload.metadata.binaryAuthorization.containerChecks.image,
    protoPayload.metadata.binaryAuthorization.containerChecks.explanation)'

Constraints và Failure Modes

Multiple Containers: Tất Cả Phải Pass

Nếu một Pod có nhiều containers (app + sidecar + init containers), tất cả phải thỏa mãn policy. Nếu 1 trong số đó bị reject → toàn bộ Pod creation bị block. Không có partial admission.

Challenge thực tế: Istio sidecar injection, logging agents, monitoring agents — tất cả container images trong Pod phải có attestation hoặc nằm trong allowlist. Thường giải quyết bằng cách đưa sidecar images vào admissionWhitelistPatterns với digest cụ thể.

Webhook Timeout và Latency

Binary Authorization webhook có timeout 10 giây. Latency chủ yếu đến từ Artifact Analysis query (tìm attestations). Trong cluster lớn với nhiều concurrent deployments, Artifact Analysis query có thể tạo bottleneck.

Với failurePolicy: Fail, timeout = deployment block. Không có automatic retry từ phía Kubernetes — developer phải retry manually. Design implication: đảm bảo Artifact Analysis SLA và Binary Authorization API SLA trong runbook production.

Eventual Consistency của Artifact Analysis

Sau khi tạo attestation occurrence, Artifact Analysis có eventual consistency — có thể mất vài giây đến 1-2 phút để Binary Authorization enforcer thấy occurrence mới. Race condition trong CI/CD:

T=0: sign-and-create attestation → occurrence được tạo
T=5s: CD pipeline trigger kubectl apply
T=5s: BinAuthz query → occurrence CHƯA visible → DENY

Giải pháp thực tế: Thêm sleep 30 sau khi tạo attestation, hoặc poll gcloud container binauthz attestations list đến khi attestation visible trước khi trigger deploy.


Tài liệu tham khảo