End-to-end secure container supply-chain template using Google Cloud Binary Authorization with KMS-backed signing, Cloud Build, and GitHub Actions via Workload Identity Federation.
This repository intentionally does not manage GKE infrastructure or policies.
A complete attestation pipeline:
- GitHub Actions → Google Cloud (OIDC / Workload Identity Federation)
- Binary Authorization attestation
- KMS-backed asymmetric signing
- Attestations stored in Container Analysis
- Custom Cloud Build service account
- Container build + push to Artifact Registry
- SBOM generation
- Infrastructure as Code for everything
- A commit is pushed to Github
- GitHub Actions authenticates to GCP using OIDC (WIF)
- GitHub impersonates a custom Cloud Build service account
- Cloud Build:
- Builds and pushes the container
- Resolves the image digest
- Signs the image using Cloud KMS
- Creates a Binary Authorization attestation
- At deploy time, Binary Authorization enforces that only attested images can run
Google provides a custom Cloud Build builder for Binary Authorization, but:
- It does not work reliably with Artifact Registry (in my experience)
- It caused repeated
A Docker registry domain must be specifiederrors
- It caused repeated
This repo uses the official gcloud CLI instead, which:
- Works consistently with Artifact Registry
- Allows explicit digest handling
- Is easier to reason about and debug
For security reasons, I've chosen to use Github SECRETS to set values for GCP project, workload identity pool, service account and GCS bucket.
NOTE: You can either remove this or manually add these secrets in the UI. Ensure that all variable values are as intended, as you will need to match these with the Terraform inputs
env:
# Project
PROJECT: ${{ secrets.GCP_PROJECT }}
WIF_PROVIDER: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
# Identity
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
# Artifacts
GAR_REPO: trusted-images
GAR_LOCATION: europe-west2-docker.pkg.dev
BUCKET: ${{ secrets.GCS_BUCKET }}
# KMS
KMS_KEY: attestation-signer-key
KMS_KEYRING: attestation-signer-ring
KMS_KEY_VERSION: 1
KMS_LOCATION: global
ATTESTOR: build-attestor
# Container Image
IMAGE: helloworld
Update terraform/inputs.auto.tfvars with suitable variable names. See terraform/variables.tf for reference
NOTE: If using GCS bucket to store state file, uncomment the "gcs block" in terraform/provider.tf:
cd terraform
terraform init
terraform plan
terraform apply --auto-approve
Attestors:
➜ ~ gcloud container binauthz attestors list
┌──────────────────────┬─────────────────────────────────────────────────┬─────────────────┐
│ NAME │ NOTE │ NUM_PUBLIC_KEYS │
├──────────────────────┼─────────────────────────────────────────────────┼─────────────────┤
│ built-by-cloud-build │ projects/PROJECT/notes/built-by-cloud-build │ 30 │
│ sbom-attestor │ projects/PROJECT/notes/sbom-attestor-note │ 1 │
└──────────────────────┴─────────────────────────────────────────────────┴─────────────────┘
Attestations made via the pipeline
gcloud container binauthz attestations list \
--attestor=sbom-attestor \
--attestor-project=PROJECT \
--artifact-url=europe-west2-docker.pkg.dev/PROJECT/GAR_REPO/helloworld@sha256:IMAGE_DIGEST