GCP GKE Binary Authorization security PCI DSS

GKE Binary Authorization - deployment odrzucony przez brak poświadczenia (attestation)

Diagnoza i naprawa odrzuconego deployment w GKE spowodowanego przez Binary Authorization: brak poświadczenia, niezgodność klucza attestor, użycie tag zamiast digest, brak anotacji break-glass.

Jerzy Kopaczewski ·
Próba wdrożenia obrazu kontenerowego na klaster GKE kończy się błędem denied by attestor lub Denied by default admission rule. No attestations found. Pipeline CI/CD jest zablokowany, a kubectl apply zwraca odmowę. Obraz jest gotowy w rejestrze, ale Binary Authorization nie przepuszcza deployment - żadne poświadczenie (attestation) nie potwierdza, że obraz przeszedł wymaganą weryfikację bezpieczeństwa.

Ten runbook opisuje diagnozę i naprawę problemu, w którym GKE Binary Authorization blokuje deployment z powodu brakującego lub niepoprawnego poświadczenia obrazu. Szczegółowe omówienie architektury GCP zgodnej z PCI DSS i roli Binary Authorization w zabezpieczaniu supply chain znajdziesz w artykule Infrastruktura GCP dla fintechu - PCI DSS, SOC2 i Google Cloud.

Objaw

Deployment na klaster GKE z włączoną polityką Binary Authorization jest odrzucany. Błąd pojawia się w output kubectl apply lub w logach pipeline CI/CD:

# kubectl apply zwraca błąd admission webhook
kubectl apply -f deployment.yaml
# Error from server (VIOLATES_POLICY): 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/build-attestor: No attestations found

# Alternatywny komunikat przy default deny rule
kubectl apply -f deployment.yaml
# Error from server: error when creating "deployment.yaml":
# admission webhook "imagepolicywebhook.image-policy.k8s.io" denied the request:
# Denied by default admission rule. No attestations found that were valid
# and signed by a key trusted by the attestor

# Sprawdzenie statusu deployment
kubectl get events --sort-by='.lastTimestamp' | grep -i denied
# LAST SEEN   TYPE      REASON    OBJECT                    MESSAGE
# 2m          Warning   Denied    pod/my-app-7b9f4d-xk2lm  Denied by BinAuthz

# Logi Admission Controller
kubectl logs -n kube-system -l app=binauthz-admission-controller --tail=20
# "msg":"image policy webhook denied","image":"gcr.io/my-project/my-app:v1.2.3",
# "reason":"no valid attestation found for image"

Problem dotyczy wyłącznie klastrów z aktywną polityką Binary Authorization. Obraz jest prawidłowo zbudowany i dostępny w rejestrze - blokada następuje na etapie admission control.

Przyczyna

Binary Authorization odrzuca deployment z czterech głównych powodów:

  1. Obraz nie jest podpisany/zaatestowany przez wymagany attestor. Pipeline CI/CD nie wykonał kroku poświadczenia po zbudowaniu obrazu, lub krok poświadczenia nie powiódł się cicho (silent failure). Jest to najczęstsza przyczyna - szczególnie po dodaniu nowego attestor do polityki bez aktualizacji pipeline.

  2. Niezgodny klucz lub wersja klucza attestor. Attestor jest skonfigurowany z kluczem KMS, który został zrotowany lub usunięty. Poświadczenie zostało podpisane starą wersją klucza, a polityka wymaga aktualnej wersji. Zdarza się po planowej rotacji kluczy KMS bez aktualizacji istniejących poświadczeń.

  3. Niezgodność digest obrazu - użyto tag zamiast digest. Binary Authorization operuje na immutable digest (sha256). Jeśli deployment odwołuje się do obrazu przez tag (np. :latest, :v1.2.3), a poświadczenie zostało utworzone dla konkretnego digest - weryfikacja nie przejdzie, bo tag mógł zostać nadpisany.

  4. Brak break-glass annotation dla wdrożeń awaryjnych. W sytuacji awaryjnej (hotfix produkcyjny, krytyczna podatność) deployment musi zawierać specjalną anotację, która pozwala ominąć politykę Binary Authorization. Bez niej nawet pilne poprawki są blokowane.

Rozwiązanie

Krok 1: Sprawdź aktywną politykę Binary Authorization na klastrze

# Wyświetl politykę Binary Authorization dla projektu
gcloud container binauthz policy export --project=my-project

# Przykładowy output:
# admissionWhitelistPatterns:
# - namePattern: gcr.io/google_containers/*
# - namePattern: gcr.io/google-containers/*
# defaultAdmissionRule:
#   enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
#   evaluationMode: REQUIRE_ATTESTATION
#   requireAttestationsBy:
#   - projects/my-project/attestors/build-attestor
#   - projects/my-project/attestors/security-scan-attestor
# clusterAdmissionRules:
#   europe-west1-b.production-cluster:
#     enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
#     evaluationMode: REQUIRE_ATTESTATION
#     requireAttestationsBy:
#     - projects/my-project/attestors/build-attestor
#     - projects/my-project/attestors/security-scan-attestor
#     - projects/my-project/attestors/qa-attestor

# Sprawdź konfigurację attestor
gcloud container binauthz attestors describe build-attestor \
  --project=my-project \
  --format="yaml(name, userOwnedGrafeasNote, publicKeys)"

Krok 2: Zweryfikuj czy poświadczenie istnieje dla danego obrazu

# Pobierz pełny digest obrazu z rejestru
gcloud container images describe gcr.io/my-project/my-app:v1.2.3 \
  --format="value(image_summary.digest)"
# Output: sha256:a1b2c3d4e5f6...

# Wylistuj atestacje dla tego digest
gcloud container binauthz attestations list \
  --project=my-project \
  --attestor=build-attestor \
  --attestor-project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6..."

# Jeśli lista jest pusta - poświadczenie nie istnieje
# Jeśli poświadczenie istnieje ale deployment jest odrzucany - sprawdź klucz

# Sprawdź atestacje od security-scan-attestor (jeśli polityka wymaga obu)
gcloud container binauthz attestations list \
  --project=my-project \
  --attestor=security-scan-attestor \
  --attestor-project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6..."

Krok 3: Utwórz brakujące poświadczenie

# Pobierz identyfikator klucza publicznego attestor
gcloud container binauthz attestors describe build-attestor \
  --project=my-project \
  --format="json(userOwnedGrafeasNote.publicKeys)"

# Utwórz atestację przy użyciu klucza KMS
gcloud container binauthz attestations sign-and-create \
  --project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6..." \
  --attestor=build-attestor \
  --attestor-project=my-project \
  --keyversion-project=my-project \
  --keyversion-location=europe-west1 \
  --keyversion-keyring=binauthz-keys \
  --keyversion-key=build-attestor-key \
  --keyversion=1

# Potwierdź utworzenie atestacji
gcloud container binauthz attestations list \
  --project=my-project \
  --attestor=build-attestor \
  --attestor-project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6..."
# Expected: atestacja widoczna na liście

Krok 4: Napraw odwołania do obrazów - użyj digest zamiast tag

# ZŁE - deployment odwołuje się do tag
# Tag może wskazywać na inny digest niż zaatestowany
spec:
  containers:
    - name: my-app
      image: gcr.io/my-project/my-app:v1.2.3

# DOBRZE - deployment odwołuje się do immutable digest
spec:
  containers:
    - name: my-app
      image: gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6...
# Automatyczna zamiana tag na digest w manifeście
IMAGE_DIGEST=$(gcloud container images describe \
  gcr.io/my-project/my-app:v1.2.3 \
  --format="value(image_summary.fully_qualified_digest)")

sed -i "s|gcr.io/my-project/my-app:v1.2.3|${IMAGE_DIGEST}|g" deployment.yaml

# Zastosuj poprawiony manifest
kubectl apply -f deployment.yaml

Krok 5: Wdrożenie awaryjne z break-glass (tylko sytuacje krytyczne)

# Break-glass annotation pozwala ominąć Binary Authorization
# UWAGA: generuje alert w Cloud Audit Logs - wymaga post-mortem
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-emergency
  annotations:
    alpha.image-policy.k8s.io/break-glass: "true"
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
      annotations:
        alpha.image-policy.k8s.io/break-glass: "true"
    spec:
      containers:
        - name: my-app
          image: gcr.io/my-project/my-app:v1.2.3-hotfix
# Wdróż z break-glass
kubectl apply -f deployment-emergency.yaml

# NATYCHMIAST po wdrożeniu: utwórz atestację dla hotfix obrazu
# i wdróż ponownie bez break-glass (compliance requirement)
gcloud container binauthz attestations sign-and-create \
  --project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:hotfix-digest..." \
  --attestor=build-attestor \
  --attestor-project=my-project \
  --keyversion-project=my-project \
  --keyversion-location=europe-west1 \
  --keyversion-keyring=binauthz-keys \
  --keyversion-key=build-attestor-key \
  --keyversion=1

Krok 6: Napraw pipeline CI/CD - dodaj krok poświadczenia

# Przykład kroku atestacji w Cloud Build (cloudbuild.yaml)
steps:
  # ... build i push steps ...
  
  - id: 'create-attestation'
    name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'bash'
    args:
      - '-c'
      - |
        IMAGE_DIGEST=$(gcloud container images describe \
          gcr.io/${PROJECT_ID}/my-app:${SHORT_SHA} \
          --format="value(image_summary.digest)")
        
        gcloud container binauthz attestations sign-and-create \
          --project=${PROJECT_ID} \
          --artifact-url="gcr.io/${PROJECT_ID}/my-app@$${IMAGE_DIGEST}" \
          --attestor=build-attestor \
          --attestor-project=${PROJECT_ID} \
          --keyversion-project=${PROJECT_ID} \
          --keyversion-location=europe-west1 \
          --keyversion-keyring=binauthz-keys \
          --keyversion-key=build-attestor-key \
          --keyversion=1
    waitFor: ['push-image']

Krok 7: Weryfikacja zgodności klucza attestor po rotacji KMS

Jeśli odmowa pojawiła się po rotacji klucza KMS, attestor wymaga aktualizacji nowej wersji klucza:

# Sprawdź aktualne wersje klucza w keyring
gcloud kms keys versions list \
  --key=build-attestor-key \
  --keyring=binauthz-keys \
  --location=europe-west1 \
  --project=my-project \
  --format="table(name, state, createTime)"

# Jeśli wersja 1 jest DISABLED a wersja 2 jest ENABLED,
# zaktualizuj attestor o nową wersję klucza
gcloud container binauthz attestors public-keys add \
  --attestor=build-attestor \
  --project=my-project \
  --keyversion-project=my-project \
  --keyversion-location=europe-west1 \
  --keyversion-keyring=binauthz-keys \
  --keyversion-key=build-attestor-key \
  --keyversion=2

# Usuń stary (wyłączony) klucz z attestor
OLD_KEY_ID=$(gcloud container binauthz attestors describe build-attestor \
  --project=my-project \
  --format="value(userOwnedGrafeasNote.publicKeys[0].id)")

gcloud container binauthz attestors public-keys remove "${OLD_KEY_ID}" \
  --attestor=build-attestor \
  --project=my-project

# Ponownie zaatestuj obrazy podpisane starym kluczem
# (wymagane tylko dla obrazów aktualnie wdrożonych lub planowanych do wdrożenia)
gcloud container binauthz attestations sign-and-create \
  --project=my-project \
  --artifact-url="gcr.io/my-project/my-app@sha256:a1b2c3d4e5f6..." \
  --attestor=build-attestor \
  --attestor-project=my-project \
  --keyversion-project=my-project \
  --keyversion-location=europe-west1 \
  --keyversion-keyring=binauthz-keys \
  --keyversion-key=build-attestor-key \
  --keyversion=2

Walidacja

Po naprawieniu poświadczeń lub pipeline potwierdź, że deployment przechodzi:

# 1. Ponów deployment
kubectl apply -f deployment.yaml
# Expected: deployment.apps/my-app configured (brak błędu admission)

# 2. Sprawdź status pod
kubectl get pods -l app=my-app
# Expected: STATUS = Running

# 3. Potwierdź w Audit Logs brak nowych denial
gcloud logging read \
  'resource.type="k8s_cluster" AND
   protoPayload.methodName="io.k8s.core.v1.pods.create" AND
   protoPayload.response.reason="VIOLATES_POLICY"' \
  --project=my-project \
  --freshness=10m \
  --format="table(timestamp, protoPayload.response.reason)"
# Expected: brak wyników (żadne nowe odrzucenia)

# 4. Sprawdź break-glass events (powinny być 0 w normalnej operacji)
gcloud logging read \
  'resource.type="k8s_cluster" AND
   protoPayload.request.metadata.annotations."alpha.image-policy.k8s.io/break-glass"="true"' \
  --project=my-project \
  --freshness=24h
# Expected: brak wyników (break-glass używane tylko w emergencies)

# 5. Zweryfikuj że attestor i klucz KMS są zgodne
gcloud container binauthz attestors describe build-attestor \
  --project=my-project \
  --format="value(userOwnedGrafeasNote.publicKeys[0].id)"
# Porównaj z wersją klucza używaną w pipeline

Binary Authorization jest krytycznym zabezpieczeniem supply chain wymaganym przez PCI DSS i SOC2. Blokada deployment oznacza, że mechanizm działa prawidłowo - problem jest w procesie poświadczenia, a nie w polityce. Nigdy nie wyłączaj Binary Authorization na produkcji. Zamiast tego napraw pipeline, aby każdy obraz był automatycznie atestowany po przejściu skanowania bezpieczeństwa. Break-glass jest wyłącznie na sytuacje awaryjne i każde jego użycie musi być udokumentowane w post-mortem.

 

Jerzy Kopaczewski

Binary Authorization blokuje deployment?

Umów bezpłatną 30-minutową rozmowę. Zaprojektujemy pipeline z automatycznym poświadczeniem obrazów, konfiguracją attestor i politykami zgodnymi z PCI DSS.