Skip to content

IAM Overview

A flat list of every GCP service account this Terraform module creates, what roles each one holds, and which Kubernetes service account (if any) impersonates it via Workload Identity.

Code: infrastructure/gcp/gke_iam.tf, cloudbuild_iam.tf, image_updater_iam.tf, plus the SA blocks inside backup.tf and tempo.tf. The photon-indexer SA is not Terraform-managed — it lives outside Terraform alongside the Photon GCS bucket (see bootstrap doc) to keep the planet index safe from terraform destroy.

Workload Identity at a glance

Workload Identity is enabled on the cluster with the pool ${project_id}.svc.id.goog. Every binding follows the same shape:

serviceAccount:${project_id}.svc.id.goog[<namespace>/<ksa-name>]

The K8s SA on the left of the arrow can call gcloud auth print-identity-token and pretend to be the GCP SA on the right.

K8s SA workload pod identity
Workload Identity
GCP SA holds IAM roles
IAM
GCS / AR / etc.
One pattern for every workload-to-GCP-resource binding. The table below enumerates each instance.

The full table

GCP SA Created in K8s SA via WI Roles What it lets the workload do
cloudbuild-worker-sa@… cloudbuild_iam.tf (none — used directly by Cloud Build) roles/logging.logWriter (project), roles/artifactregistry.writer (project), roles/container.developer (project) Cloud Build can write logs, push Docker images to both registries, and (future) deploy to GKE.
argocd-image-updater-sa@… image_updater_iam.tf argocd/argocd-image-updater roles/artifactregistry.reader (project) Argo CD Image Updater lists image tags in both Artifact Registry repos.
cnpg-backup-sa@… backup.tf data/postgres-dev and data/postgres-prod roles/storage.objectAdmin on tomoda-db-backups-${project_id} CNPG Barman writes WAL + base backups for both Postgres clusters into the backup bucket.
photon-indexer@… manual bootstrap (bootstrap doc) platform/photon-indexer roles/storage.objectAdmin on ${project_id}-photon-index-usc1 Photon indexer (CronJob, currently suspended; or scripts/photon-index-local.sh) uploads index tarballs. SA + bucket detached from Terraform so destroy can't touch the $500-per-rebuild planet index.
tempo@… tempo.tf monitoring/tempo roles/storage.objectAdmin on ${project_id}-tomoda-traces Grafana Tempo writes trace chunks to (and reads from) the traces bucket.
cost-exporter@… cost_exporter_iam.tf monitoring/cost-exporter roles/bigquery.jobUser (project), roles/bigquery.dataViewer on the billing_export dataset Cost-exporter queries the BigQuery billing export for GCP month-to-date spend.
Default compute SA 287267207777-compute@developer.gserviceaccount.com gke_iam.tf (binding only) (implicitly used by GKE nodes) roles/artifactregistry.reader (project) Every GKE node can pull images from both Artifact Registry repos.

Cross-references

Where to read more Doc
Cloud Build SA + IAM in context Cloud Build
Image Updater flow end-to-end Artifact Registry
CNPG backup wiring Backup
Photon indexer wiring Photon Indexer
GKE node identity & cluster shape GKE

Things worth knowing

  • No SA keys. Every binding above uses Workload Identity or the platform's own impersonation chain. There are no JSON key files in this Terraform — and there should never be. If you see one being added, push back.
  • Project-level roles. roles/artifactregistry.writer and roles/artifactregistry.reader are granted at the project level, not per-repo. Adding a third Artifact Registry repo does not require new IAM.
  • Bucket-scoped roles. roles/storage.objectAdmin is always granted on a specific bucket via google_storage_bucket_iam_member, never at the project level. Adding a new bucket means adding a new binding.
  • The Cloud Build platform SA (287267207777@cloudbuild.gserviceaccount.com) is granted roles/iam.serviceAccountUser on cloudbuild-worker-sa (cloudbuild_iam.tf). That binding is what lets Cloud Build impersonate the worker SA when executing a build.
  • 287267207777 is the project number for development-485000 — it appears in multiple bindings (default compute SA, Cloud Build platform SA). If you ever migrate to a new project, every binding using that literal will need to be updated.