Free lesson · GenAI Platform Engineering
Sign images with Cosign and enforce Binary Authorization on GKE
You will implement a complete container supply chain security pipeline using Cosign and GKE Binary Authorization. Cosign signing: generate a key pair using Google Cloud KMS (cosign generate-key-pair --kms gcpkms://...). After every successful CI build, sign the image: cosign sign --key gcpkms://... <image>@<digest>. The signature is stored alongside the image in Artifact Registry as an OCI artifact. Cosign verification: before any deployment, verify the signature: cosign verify --key gcpkms://... <image>. Binary Authorization: enable Binary Authorization on your GKE cluster. Create an attestor that requires Cosign signatures. Configure the policy: ALLOW_ONLY images from your Artifact Registry that have a valid Cosign signature from your CI attestor. Test: try to deploy an unsigned image — GKE should reject it with an admission webhook error. Deploy a signed image — GKE should accept it. End-to-end flow: code push → Tekton pipeline builds → Trivy scans → Syft generates SBOM → Cosign signs → deploy → Binary Authorization verifies → GKE admits. This is the supply chain security pattern used at Google, Stripe, and Shopify.
Course: DevOps Foundations for GenAI Engineers · Chapter 3 · Container Image CI/CD
Free to read — no subscription required.
Introduction
When you scan an image for vulnerabilities, you learn what is inside it — but you have not proven who built it or whether anyone modified it between the scan and kubectl apply. Teams that stop at scanning ship signed-by-nobody images to production, and a single compromised CI runner can quietly push a tampered binary that passes every other gate. By the end of this lesson you will be able to sign image digests with Cosign using a KMS-backed key, configure Binary Authorization on GKE so unsigned images are rejected at admission, and prove the gate works by watching the cluster refuse an unsigned tag.
Key Terminology
- Cosign — Sigstore CLI that signs container image digests and stores the signature as an OCI artifact next to the image; in this lesson it produces the cryptographic attestation that Binary Authorization checks.
- Binary Authorization — GKE admission controller that intercepts every pod create and refuses images that lack a required attestor's signature; it is the runtime enforcement point that makes signing more than a formality.
- Attestor — A Binary Authorization resource that names the public key allowed to vouch for an image; the policy rejects any image not signed by an attestor it lists.
- KMS-backed key — A Cosign signing key whose private half lives in Google Cloud KMS instead of on disk, so the CI pipeline signs by API call and the key cannot be exfiltrated from a runner.
- Image digest — The immutable SHA256 of the image manifest (e.g.
repo@sha256:…); Cosign signs digests, not tags, because tags can be retagged onto different bits later.
Concepts
Cosign signs digests, not tags
The signature is bound to a SHA256 manifest digest, so retagging an image after signing produces a reference that no signature covers. Verification therefore always resolves the tag to its digest first, then asks Cosign whether that digest carries a valid signature from the configured KMS key. Scanning and signing are complementary: Trivy proves the contents are clean at scan time, Cosign proves the bits have not changed since (see Code Walkthrough).
Binary Authorization is the enforcement point
A policy alone does nothing; GKE's admission webhook is what refuses the pod. The webhook reads the cluster policy, resolves each container reference to a digest, and looks up the matching signature in Artifact Registry. If the digest is not signed by an attestor the policy requires, the pod create returns an image not attested by required attestor error and the workload never runs. System namespaces (kube-system, GKE-managed add-ons) are exempted by default; your namespaces are not.
The end-to-end supply chain gate
Each arrow is a gate that must pass before the next step runs, and the whole chain is verifiable after the fact — given an image digest, anyone with the public key can re-prove the signature without trusting the CI logs.
Code Walkthrough
This walkthrough demonstrates the two concepts together: a CI step that signs a digest with a KMS-backed Cosign key, and a verification gate that re-checks the signature the way Binary Authorization will at admission time.
Code snippet python
1import subprocess 2import sys 3from dataclasses import dataclass 4 5KMS_KEY_REF = ( 6 "gcpkms://projects/my-project/locations/global" 7 "/keyRings/cosign/cryptoKeys/image-signing" 8 "/cryptoKeyVersions/1" 9) 10 11@dataclass 12class SigningResult: 13 image: str 14 digest: str 15 signed: bool 16 verified: bool 17 error: str = "" 18 19def get_image_digest(image: str) -> str: 20 result = subprocess.run( 21 ["docker", "inspect", "--format={{index .RepoDigests 0}}", image], 22 capture_output=True, text=True, 23 ) 24 if result.returncode != 0: 25 raise RuntimeError(f"Failed to get digest: {result.stderr}") 26 return result.stdout.strip() 27 28def sign_image(image_with_digest: str) -> bool: 29 result = subprocess.run( 30 ["cosign", "sign", "--key", KMS_KEY_REF, "--yes", image_with_digest], 31 capture_output=True, text=True, 32 ) 33 if result.returncode != 0: 34 print(f"Signing failed: {result.stderr}") 35 return False 36 return True 37 38def verify_image(image_with_digest: str) -> bool: 39 result = subprocess.run( 40 ["cosign", "verify", "--key", KMS_KEY_REF, image_with_digest], 41 capture_output=True, text=True, 42 ) 43 return result.returncode == 0 44 45def sign_and_verify(image: str) -> SigningResult: 46 digest_ref = get_image_digest(image) 47 if not sign_image(digest_ref): 48 return SigningResult(image, digest_ref, False, False, "Signing failed") 49 verified = verify_image(digest_ref) 50 return SigningResult( 51 image=image, digest=digest_ref, 52 signed=True, verified=verified, 53 error="" if verified else "Verification failed after signing", 54 ) 55 56if __name__ == "__main__": 57 result = sign_and_verify(sys.argv[1]) 58 print(result) 59 sys.exit(0 if result.verified else 1)
- Lines 5-9:
KMS_KEY_REFis the fully-qualified Cloud KMS URI Cosign expects; pinning the key version (/cryptoKeyVersions/1) means a rotated key cannot silently change which signatures verify. - Lines 19-26:
get_image_digestresolves the tag to itsrepo@sha256:…form viadocker inspect; signing the digest (not the tag) is what binds the signature to a specific manifest. - Lines 28-36:
sign_imageshells out tocosign sign --key gcpkms://…; the private key never leaves KMS, so a compromised CI runner can request signatures but cannot exfiltrate the key. - Lines 38-43:
verify_imageis the exact check the Binary Authorization webhook performs at admission — running it in CI catches signature-storage failures before they become rejected deploys. - Lines 45-54:
sign_and_verifychains the two so a CI step fails loudly if a signature was produced but cannot be re-verified, which is the only failure mode that would slip through to GKE.
You'll know it works when sign_and_verify exits 0 against a freshly built digest, kubectl run of that digest is admitted by your GKE cluster, and kubectl run of an unsigned tag of the same image is rejected with image not attested by required attestor visible in kubectl get events.
Do's and Don'ts
Do's
- ✓Do sign the digest, not the tag — tags are mutable, so a tag-bound signature can be reused on different bits; a digest-bound signature cannot.
- ✓Do store the signing key in Cloud KMS — keeping the private key out of CI runners means a compromised runner can request signatures but cannot steal the key.
- ✓Do verify in CI immediately after signing — running
cosign verifyin the same job catches signature-publish failures before they reach the Binary Authorization webhook.
Don'ts
- ✗Don't exempt your application namespaces from the policy — leaving a namespace unenforced turns the whole gate into theater, since attackers will deploy there.
- ✗Don't share one Cosign key across environments — separate keys for staging and prod mean a leaked staging key cannot sign images that prod will admit.
- ✗Don't rely on scanning alone — Trivy proves contents at scan time; without signing, anything can be swapped in between the scan and
kubectl apply.
This lesson is free to read. Its 3 hands-on labs — real code, in a cloud IDE — are part of the GenAI Platform Engineering subscription.
From · cancel anytime
More free lessons in DevOps Foundations for GenAI Engineers
- Ch 2Configure CI to run on GKE self-hosted runners
- Ch 3Build optimized Docker images for AI applications
- Ch 3Automate image builds with GitHub Actions
- Ch 3Sign images with Cosign and enforce Binary Authorization on GKEYou are here
- Ch 3Build multi-architecture images for GKE
- Ch 4Install ArgoCD and deploy first application
- Ch 4Compare GitOps controllers: ArgoCD ApplicationSet vs Flux CD