Skip to main content

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?

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
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:
  1. CI/CD builds and tests image
  2. Push image to Artifact Registry
  3. Sign image digest with KMS key
  4. Create attestation (proof of signature)
  5. On deployment, GKE checks attestation
  6. If valid → Deploy; If invalid → Block

Quick Setup (20 minutes)

1

Enable APIs

gcloud services enable \
  binaryauthorization.googleapis.com \
  containeranalysis.googleapis.com \
  containerscanning.googleapis.com \
  --project=PROJECT_ID
2

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
3

Enable on GKE Cluster

  • Terraform
  • gcloud CLI
terraform/environments/gcp-prod/terraform.tfvars
enable_binary_authorization    = true
binary_authorization_mode      = "ENFORCING"
terraform apply
4

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.
5

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

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

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

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
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
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):
RequirementImplementation
ProvenanceAttestations prove build provenance
Non-falsifiableKMS signatures can’t be forged
AuditableAll attestations logged
HermeticOnly CI/CD can create attestations

Migration Path

1

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

2. Test Signing in CI/CD

Add signing step to CI/CD, verify attestations created.Monitor for 1 week, ensure no failures.
3

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

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

1

Set Up Binary Authorization

./deployments/security/binary-authorization/setup-binary-auth.sh PROJECT_ID production
2

Integrate with CI/CD

Add image signing step to GitHub Actions workflow
3

Enable on Cluster

enable_binary_authorization = true
binary_authorization_mode = "ENFORCING"
4

Monitor Denials

gcloud logging read 'protoPayload.response.allow=false' --limit=10