
Introduction: The “Untrusted Code” Problem
In the world of Kubernetes, kubectl apply -f deployment.yaml is a powerful command. Too powerful.
By default, a GKE cluster will happily try to run any container image you tell it to, from any registry, regardless of its origin or vulnerability status. This is a massive security gap. In a modern DevSecOps environment, we need a way to cryptographically prove: “This container was built by our trusted CI/CD pipeline, passed all security scans, and hasn’t been tampered with.”
The solution is Google Cloud Binary Authorisation.
Binary Authorisation is a deploy-time security control that acts as a gatekeeper for GKE. It ensures that only images with a valid, verifiable “signature” (attestation) from a trusted authority are allowed to run.
In this article, we will build a secure supply chain where Cloud Build signs an image, and GKE rejects any attempt to deploy it without that signature.
The Architecture: The Trust Pipeline
We are building a chain of trust. The developer pushes code, Cloud Build builds the container, signs it using a private key from Cloud KMS, and stores the signature. When GKE tries to pull the image, it verifies that signature using the corresponding public key.

Core Components Explained
- Cloud Key Management Service (KMS): Stores the cryptographic keys used for signing. We use an asymmetric key pair (private key to sign, public key to verify).
- Attestor: A Google Cloud resource that represents a trusted identity. It links a specific KMS key to a “Note” in Container Analysis.
- Cloud Build: Our CI/CD platform. It will build the image and act as the “signer,” creating an attestation.
- Binary Authorisation Policy: The rule set on the GKE cluster that enforces signature verification.
Implementation Guide: Step-by-Step
Let’s define our variables. We have one project: my-secure-project.
Prerequisites: API Enablement
Before starting, ensure the necessary Google Cloud APIs are enabled for your project:
gcloud services enable \
container.googleapis.com \
cloudbuild.googleapis.com \
binaryauthorization.googleapis.com \
containeranalysis.googleapis.com \
cloudkms.googleapis.com
Phase 1: Establishing Trust (Keys & Attestors)
Step 1: Create the Asymmetric Key Pair in KMS This key is the root of our trust. The private key will be used by Cloud Build to sign images.
# Create a KeyRing
gcloud kms keyrings create binauthz-ring --location global
# Create an Asymmetric Signing Key
gcloud kms keys create build-signer-key \
--keyring binauthz-ring \
--location global \
--purpose asymmetric-signing \
--default-algorithm rsa-sign-pkcs1-4096-sha512
Step 2: Create the Container Analysis Note A “Note” is a piece of metadata that will be attached to container images. Think of it as a blank label waiting to be stamped.
curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
--data '{"name": "projects/my-secure-project/notes/build-provenance-note", "attestation": {"hint": {"human_readable_name": "CI/CD Build Pipeline"}}}' \
"https://containeranalysis.googleapis.com/v1/projects/my-secure-project/notes?noteId=build-provenance-note"
Step 3: Create the Attestor Now we link the Note to our KMS key. This creates the “Attestor” entity that GKE will trust.
gcloud container binauthz attestors create build-attestor \
--attestation-authority-note=build-provenance-note \
--attestation-authority-note-project=my-secure-project
# Link the KMS public key to the attestor
gcloud container binauthz attestors public-keys add \
--attestor=build-attestor \
--keyversion=1 \
--keyname=build-signer-key \
--keyring=binauthz-ring \
--location=global
Phase 2: The Build & Sign Process
We need to configure Cloud Build to sign images after building them.
Step 1: Grant Permissions to Cloud Build The Cloud Build service account needs permission to view the Note, use the KMS private key, and create attestations.
PROJECT_NUMBER=$(gcloud projects describe my-secure-project --format='value(projectNumber)')
CB_SA="${@cloudbuild.gserviceaccount.com">PROJECT_NUMBER}@cloudbuild.gserviceaccount.com"
# Allow viewing the Note
gcloud projects add-iam-policy-binding my-secure-project \
--member="serviceAccount:${CB_SA}" --role="roles/containeranalysis.notes.viewer"
# Allow attaching occurrences to the Note
gcloud projects add-iam-policy-binding my-secure-project \
--member="serviceAccount:${CB_SA}" --role="roles/containeranalysis.notes.attacher"
# Allow using the KMS key for signing
gcloud kms keys add-iam-policy-binding build-signer-key \
--keyring binauthz-ring --location global \
--member="serviceAccount:${CB_SA}" --role="roles/cloudkms.signerVerifier"
Step 2: The cloudbuild.yaml pipeline This is a standard build, followed by a dedicated "sign" step using Google's binauthz-attestation builder.
steps:
# 1. Build and push the container image
- name: "gcr.io/cloud-builders/docker"
entrypoint: "bash"
args:
- "-c"
- |
docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$COMMIT_SHA .
docker push us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$COMMIT_SHA
# Save the digest to a file for the signing step
docker inspect --format='{{range .RepoDigests}}{{println .}}{{end}}' us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$COMMIT_SHA | grep "us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app" > /workspace/image-digest.txt
# 2. Sign the specific digest
- name: "gcr.io/google.com/cloudsdktool/cloud-sdk"
entrypoint: "bash"
args:
- "-c"
- |
# Read the exact digest we just pushed
DIGEST=$(cat /workspace/image-digest.txt)
gcloud container binauthz attestations sign-and-create \
--artifact-url="$DIGEST" \
--attestor="build-attestor" \
--attestor-project="$PROJECT_ID" \
--keyversion-project="$PROJECT_ID" \
--keyversion-location="global" \
--keyversion-keyring="binauthz-ring" \
--keyversion-key="build-signer-key" \
--keyversion="1"
Phase 3: Enforcing the Policy on GKE
Step 1: Enable Binary Authorisation on the Cluster If creating a new cluster, add–binauthz-evaluation-mode=PROJECT_SINGLETON_POLICY_ENFORCE. For an existing cluster:
gcloud container clusters update my-cluster \
--region=us-central1 \
--binauthz-evaluation-mode=PROJECT_SINGLETON_POLICY_ENFORCE
Step 2: Configure the Policy Binary Authorisation policies are cluster-scoped. We will configure a rule for my-cluster that enforces our attestation.
Important Note on Cluster Scope: In the clusterAdmissionRules section below, the key format depends on your cluster type:
- If using a Regional cluster: region.cluster-name (e.g., us-central1.my-cluster)
- If using a Zonal cluster: zone.cluster-name (e.g., us-central1-a.my-cluster)
- Check your cluster location with: gcloud container clusters list
(Save this as policy.yaml)
name: projects/my-secure-project/policy
globalPolicyEvaluationMode: ENABLE
# Default rule: Block everything that isn't whitelisted or attested
defaultAdmissionRule:
evaluationMode: ALWAYS_DENY
enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
# Whitelists: Allow system images and specific 3rd party tools
admissionWhitelistPatterns:
- namePattern: quay.io/example-monitor/*
# --- ADDED: Google System Images (Required for GKE Stability) ---
- namePattern: gcr.io/google_containers/*
- namePattern: gcr.io/google-containers/*
- namePattern: k8s.gcr.io/*
- namePattern: gke.gcr.io/*
- namePattern: gcr.io/stackdriver-agents/*
# Cluster-specific rule: Enforce attestation for your specific cluster
clusterAdmissionRules:
# Remember to change 'us-central1.my-cluster' to your specific region/zone and cluster name
us-central1.my-cluster:
evaluationMode: REQUIRE_ATTESTATION
enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
requireAttestationsBy:
- projects/my-secure-project/attestors/build-attestor
Note: We set defaultAdmissionRule to ALWAYS_DENY to ensure a secure-by-default posture. Crucially, we have populated admissionWhitelistPatterns with standard Google registries (such as gke.gcr.io and k8s.gcr.io). This ensures that essential system workloads (like kube-dns) are not accidentally blocked by our strict policy, while still enforcing signature verification on all applications deployed to my-cluster.
Apply the policy:
gcloud container binauthz policy import policy.yaml
Final Verification
- Test Deny: Try to deploy an unsigned image (e.g., nginx:latest) to the cluster. GKE will reject it with an error mentioning Binary Authorisation.
- Test Allow: Run the Cloud Build pipeline to create and sign your image. Then, deploy that specific signed image digest to the cluster. GKE will verify the signature and allow the deployment.
Emergency Protocols: Breaking Glass
In a critical incident (e.g., Cloud Build is down, but you must deploy a hotfix), you can bypass Binary Authorisation without needing special IAM permissions to edit the policy. You only need standard Kubernetes permissions to deploy workloads.
The security mechanism here is not prevention, but auditing. When you use this bypass, GKE admits the deployment but immediately generates a high-severity entry in Cloud Audit Logs.
To bypass the check, simply add the break-glass annotation to your Pod specification:
apiVersion: v1
kind: Pod
metadata:
labels:
image-policy.k8s.io/break-glass: "true"
spec:
containers:
- name: nginx
image: nginx:latest
This will allow the deployment but will generate a specific “Break Glass” entry in Cloud Audit Logs, alerting your security team that an emergency bypass occurred.
Addendum: Troubleshooting Common Failures
Even with a perfect setup, deployments can fail. Use this guide to diagnose the most common Binary Authorisation errors.
1. Error: Denied by Attestor
- The deployment is blocked with a message stating Image … denied by attestor …. The image was not signed, or the signature does not match the specific Attestor defined in the policy.
- Verify Execution: Check Cloud Build logs to ensure the “Sign” step ran successfully.
- Check Digest: Ensure you are deploying the exact image digest (e.g., image@sha256:abc…) that was built. Binary Authorisation blocks mutable tags like :latest by default because they can change after signing.
2. Error: PermissionDenied on KMS
- The Cloud Build log fails at the signing step with Permission denied. The Cloud Build Service Account is missing the roles/cloudkms.signerVerifier role on the specific cryptographic key.
- Fix: Re-run the IAM binding command to grant the Service Account access to the specific KeyRing/Key.
3. Error: Attestor not found
- GKE or Cloud Build complains it cannot locate the Attestor resource. Project mismatch. The command is looking for the Attestor in your current gcloud config project, but the Attestor usually resides in a dedicated security project.
- Fix: Always specify the flag –attestor-project=my-secure-project explicitly in your CLI commands.
Architectural Analysis
Pros
- Cryptographic Certainty: Prevents “man-in-the-middle” attacks on your image registry. You know exactly what code is running.
- Policy-as-Code: Deployment rules are centralised, version-controlled, and auditable.
- Compliance: Meets strict regulatory requirements for software supply chain security (e.g., SSDF, SLSA).
Cons
- Complexity: Significant setup overhead involving KMS, IAM, and Container Analysis APIs.
- Operational Friction: Requires disciplined pipeline management. Developers cannot simply “push and deploy” from their laptops; everything must go through the trusted signer.
Conclusion
Securing the software supply chain is no longer optional. Binary Authorisation provides the missing link between your CI/CD process and your runtime environment, ensuring that the trust you build in your pipeline is cryptographically enforced at the cluster gate. It’s a fundamental pillar of a mature DevSecOps posture on Google Cloud.
References & Further Reading
- Binary Authorisation Overview: Official Documentation
- Creating Attestors: Setting up Attestors
- Cloud Build Integration: Signing images with Cloud Build
Originally published at https://lineargs.dev.
Securing the Supply Chain: Enforcing Trust from Artifact Registry to GKE with Binary Authorisation was originally published in Google Cloud – Community on Medium, where people are continuing the conversation by highlighting and responding to this story.
Source Credit: https://medium.com/google-cloud/securing-the-supply-chain-enforcing-trust-from-artifact-registry-to-gke-with-binary-authorisation-63d229027988?source=rss—-e52cf94d98af—4
