Overview
Binary Authorization is a deploy-time security control that ensures only trusted container images run on GKE. Images must be cryptographically signed and verified before deployment, preventing supply chain attacks and unauthorized images.
Image Signing CI/CD signs images with KMS keys
Policy Enforcement GKE blocks unsigned images
Attestations Proof of CI/CD approval
Audit Trail All denials logged to Cloud Logging
Why Binary Authorization?
Prevent Unauthorized Images
Threat : Attacker pushes malicious image to registryProtection : Only images signed by your CI/CD can deployExample blocked scenarios :
Directly pushed images (bypassing CI/CD)
Images from unknown registries
Tampered images (signature verification fails)
Compliance : SLSA Level 3, SOC 2, HIPAARequirement : Prove images were built by authorized systemsBinary Auth provides :
Cryptographic proof of build provenance
Attestations from CI/CD pipeline
Immutable audit trail
Multi-Environment Control
Problem : Dev images shouldn’t reach productionSolution : Different attestors per environmentImplementation :
Dev: Allow all (no signing)
Staging: Require staging attestor (audit mode)
Prod: Require prod attestor (enforcing mode)
Scenario : Compromised CI/CD pipelineResponse : Revoke KMS key, all signed images instantly blockedRecovery : Create new KMS key, re-sign trusted images
Architecture
Flow :
CI/CD builds and tests image
Push image to Artifact Registry
Sign image digest with KMS key
Create attestation (proof of signature)
On deployment, GKE checks attestation
If valid → Deploy; If invalid → Block
Quick Setup (20 minutes)
Enable APIs
gcloud services enable \
binaryauthorization.googleapis.com \
containeranalysis.googleapis.com \
containerscanning.googleapis.com \
--project=PROJECT_ID
Run Setup Script
./deployments/security/binary-authorization/setup-binary-auth.sh \
PROJECT_ID production
What it creates :
KMS key ring and signing key
Container Analysis note
Attestor resource
Binary Authorization policy
Sign First Image
./deployments/security/binary-authorization/sign-image.sh \
PROJECT_ID production \
us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph:v1.0.0
Creates attestation for the image.
Verify Policy
# Try deploying unsigned image (should fail)
kubectl run test --image=nginx:latest
# Should see error:
# Error: admission webhook denied the request
# Deploy signed image (should succeed)
kubectl apply -k deployments/overlays/production-gke
Policy Configuration
Policy Modes
Enforcing (Production)
Audit (Staging)
Disabled (Development)
evaluationMode : REQUIRE_ATTESTATION
enforcementMode : ENFORCING_MODE
Behavior : Blocks unsigned imagesUse case : Production environmentsRisk : Zero tolerance for unsigned images
Environment-Specific Policies
Production Policy
Staging Policy
apiVersion : binaryauthorization.googleapis.com/v1
kind : Policy
metadata :
name : production-policy
spec :
globalPolicyEvaluationMode : ENABLE
defaultAdmissionRule :
evaluationMode : REQUIRE_ATTESTATION
enforcementMode : ENFORCING_MODE
requireAttestationsBy :
- projects/PROJECT_ID/attestors/production-attestor
# Exception for system images
clusterAdmissionRules :
us-central1.production-mcp-server-langgraph-gke :
evaluationMode : REQUIRE_ATTESTATION
enforcementMode : ENFORCING_MODE
requireAttestationsBy :
- projects/PROJECT_ID/attestors/production-attestor
# Allow GKE system images
admissionWhitelistPatterns :
- namePattern : gcr.io/gke-release/*
- namePattern : gke.gcr.io/*
- namePattern : k8s.gcr.io/*
Image Signing in CI/CD
GitHub Actions Integration
.github/workflows/deploy-production-gke.yaml
name : Deploy to Production GKE
on :
push :
tags :
- 'v*'
jobs :
build-sign-deploy :
runs-on : ubuntu-latest
permissions :
contents : read
id-token : write # For Workload Identity Federation
steps :
- uses : actions/checkout@v4
- name : Authenticate to GCP
uses : google-github-actions/auth@v2
with :
workload_identity_provider : 'projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github/providers/github'
service_account : 'github-actions@PROJECT_ID.iam.gserviceaccount.com'
- name : Configure Docker for Artifact Registry
run : gcloud auth configure-docker us-central1-docker.pkg.dev
- name : Build Image
run : |
docker build -t us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph:${{ github.ref_name }} .
- name : Push Image
run : |
docker push us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph:${{ github.ref_name }}
- name : Sign Image
run : |
IMAGE_URL="us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph:${{ github.ref_name }}"
IMAGE_DIGEST=$(gcloud artifacts docker images describe $IMAGE_URL --format='get(image_summary.digest)')
# Create attestation
gcloud beta container binauthz attestations sign-and-create \
--artifact-url="us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph@${IMAGE_DIGEST}" \
--attestor=production-attestor \
--attestor-project=PROJECT_ID \
--keyversion-project=PROJECT_ID \
--keyversion-location=us-central1 \
--keyversion-keyring=production-binauthz \
--keyversion-key=attestor-key \
--keyversion=1
- name : Deploy to GKE
run : |
kubectl apply -k deployments/overlays/production-gke
``` bash
``` bash Manual Signing Script
#!/bin/bash
# deployments/security/binary-authorization/sign-image.sh
PROJECT_ID="$1"
ENVIRONMENT="$2"
IMAGE_URL="$3"
# Get image digest
IMAGE_DIGEST=$(gcloud artifacts docker images describe "$IMAGE_URL" \
--format='get(image_summary.digest)')
# Extract registry and image name
REGISTRY=$(echo "$IMAGE_URL" | cut -d'/' -f1)
REPO_PATH=$(echo "$IMAGE_URL" | cut -d':' -f1 | cut -d'/' -f2-)
ARTIFACT_URL="${REGISTRY}/${REPO_PATH}@${IMAGE_DIGEST}"
echo "Signing image : $ARTIFACT_URL"
# Create attestation
gcloud beta container binauthz attestations sign-and-create \
--artifact-url="$ARTIFACT_URL" \
--attestor="${ENVIRONMENT}-attestor" \
--attestor-project="$PROJECT_ID" \
--keyversion-project="$PROJECT_ID" \
--keyversion-location=us-central1 \
--keyversion-keyring="${ENVIRONMENT}-binauthz" \
--keyversion-key=attestor-key \
--keyversion=1
echo "✅ Image signed successfully"
Verification & Troubleshooting
Check Policy Status
# View current policy
gcloud container binauthz policy export
# Check attestor
gcloud container binauthz attestors describe production-attestor \
--project=PROJECT_ID
# List attestations for image
gcloud container binauthz attestations list \
--artifact-url= "us-central1-docker.pkg.dev/PROJECT_ID/mcp-production/mcp-server-langgraph@sha256:ABC123" \
--attestor=production-attestor \
--attestor-project=PROJECT_ID
View Denials in Logs
# Check Binary Auth denials
gcloud logging read \
'protoPayload.serviceName="binaryauthorization.googleapis.com" AND protoPayload.response.allow=false' \
--limit=50 \
--format=json
# Denied image details
gcloud logging read \
'resource.type="k8s_cluster" AND protoPayload.request.spec.containers[].image=~"mcp-server"' \
--limit=10
Common Issues
Error: Image not attested
Symptom : Deployment blocked with “image not attested by projects/PROJECT/attestors/production-attestor”Cause : Image hasn’t been signed by CI/CDSolution :# Verify attestation exists
gcloud container binauthz attestations list \
--artifact-url=IMAGE_URL@DIGEST \
--attestor=production-attestor
# If missing, sign manually
./deployments/security/binary-authorization/sign-image.sh \
PROJECT_ID production IMAGE_URL
Error: Attestor not found
Symptom : “attestor not found: projects/PROJECT/attestors/production-attestor”Cause : Attestor resource not createdSolution :# Re-run setup
./deployments/security/binary-authorization/setup-binary-auth.sh \
PROJECT_ID production
GKE system images blocked
Symptom : System pods (kube-proxy, node-exporter) failing to startCause : Missing admission whitelist patternsSolution : Add to policy:admissionWhitelistPatterns :
- namePattern : gcr.io/gke-release/*
- namePattern : gke.gcr.io/*
- namePattern : k8s.gcr.io/*
Symptom : CI/CD can’t sign images - “Permission denied on KMS key”Solution :# Grant CI/CD service account KMS permissions
gcloud kms keys add-iam-policy-binding attestor-key \
--location=us-central1 \
--keyring=production-binauthz \
--member= "serviceAccount:github-actions@PROJECT_ID.iam.gserviceaccount.com" \
--role= "roles/cloudkms.signerVerifier" \
--project=PROJECT_ID
Security Best Practices
Separate keys per environment
# Production key
production-binauthz/attestor-key
# Staging key (different key ring)
staging-binauthz/attestor-key
# Development (no signing required)
Benefit : Key compromise doesn’t affect all environments
Rotate KMS keys regularly
# Create new key version (automatic rotation)
gcloud kms keys versions create \
--location=us-central1 \
--keyring=production-binauthz \
--key=attestor-key
Enable KMS key rotation policy
gcloud kms keys update attestor-key \
--location=us-central1 \
--keyring=production-binauthz \
--rotation-period=90d \
--next-rotation-time=2025-04-01T00:00:00Z
Audit attestations regularly
# List all attestations in last 7 days
gcloud logging read \
'protoPayload.serviceName="binaryauthorization.googleapis.com" AND protoPayload.methodName="google.cloud.binaryauthorization.v1.BinauthzManagementService.CreateAttestor"' \
--freshness=7d
Principle of least privilege
# CI/CD SA: Only signer role (can create attestations)
gcloud kms keys add-iam-policy-binding attestor-key \
--role=roles/cloudkms.signerVerifier \
--member= "serviceAccount:ci-cd@PROJECT.iam.gserviceaccount.com"
# GKE SA: Only verifier role (can check attestations)
# Automatic via Workload Identity
Compliance & Auditing
SOC 2 / HIPAA Requirements
Control Objectives Met
Audit Evidence
✅ CC6.1 : Logical access controls
Only signed images can deploy
Attestations prove CI/CD approval
✅ CC6.6 : Logical access segregation
Production attestor separate from dev/staging
KMS keys isolated per environment
✅ CC7.2 : System monitoring
All denials logged to Cloud Logging
Attestation creation audited
✅ CC8.1 : Change management
All deployments require signed images
Git commit → CI/CD → Attestation chain
SLSA Level 3 Compliance
Binary Authorization helps achieve SLSA Level 3 (Supply Chain Levels for Software Artifacts):
Requirement Implementation Provenance Attestations prove build provenance Non-falsifiable KMS signatures can’t be forged Auditable All attestations logged Hermetic Only CI/CD can create attestations
Migration Path
1. Enable in Audit Mode (Dev)
# Development cluster - audit only
gcloud container clusters update mcp-dev-gke \
--binauthz-evaluation-mode=POLICY_BINDINGS \
--region=us-central1
No enforcement, just logging.
2. Test Signing in CI/CD
Add signing step to CI/CD, verify attestations created. Monitor for 1 week, ensure no failures.
3. Enable Audit Mode (Staging)
# Staging cluster - audit mode
# Terraform: binary_authorization_mode = "DRYRUN_AUDIT_LOG_ONLY"
terraform apply
Monitor denials, fix any unsigned images.
4. Enable Enforcing (Production)
# Production cluster - enforcing
# Terraform: binary_authorization_mode = "ENFORCING"
terraform apply
Only after :
All images signed
No denials in staging for 2 weeks
Rollback plan tested
Next Steps
Set Up Binary Authorization
./deployments/security/binary-authorization/setup-binary-auth.sh PROJECT_ID production
Integrate with CI/CD
Add image signing step to GitHub Actions workflow
Enable on Cluster
enable_binary_authorization = true
binary_authorization_mode = "ENFORCING"
Monitor Denials
gcloud logging read 'protoPayload.response.allow=false' --limit=10