Lesson 9 · Identity, secrets & operations
IAM & Workload Identity
The cross-course payoff — the Terraform behind every keyless credential you've used for six courses.
Your win: explain GCP IAM, and read the Workload Identity binding that
is the source of the iam.gke.io/gcp-service-account annotation (C3/C6) and
the keyless CI auth (C5). This is the loop the whole finale exists to close.
IAM in one breath
GCP IAM answers "who may do what to which resource": a member
(a user, group, or service account) is granted a role (a
bundle of permissions) on a resource.1 In Terraform:
google_service_account creates an identity, google_project_iam_member
grants it a role. A service account (GSA) is a non-human identity — for a
workload or a CI job.
Workload Identity — the binding you've been using since Course 3
Recall the recurring theme: pods and CI reach Google APIs (Cloud SQL, KMS, GCS) with no static key files. The mechanism is Workload Identity: a Kubernetes ServiceAccount (KSA) is allowed to impersonate a GCP service account (GSA). The link is an IAM binding — and it's Terraform.
resource "google_service_account_iam_member" "workload_identities" { service_account_id = # the per-service GSA (from sa.tf:58) role = "roles/iam.workloadIdentityUser" # :52 member = format("serviceAccount:%s[%s/%s]", var.gke_identity_namespace, namespace, name) # :53 }The member is
serviceAccount:PROJECT.svc.id.goog[<ns>/<ksa>]. That
single resource is exactly what makes the iam.gke.io/gcp-service-account
annotation work (Course 3 L6, Course 6): the annotation names the GSA; this binding
grants the KSA permission to be it. No binding → the annotation is inert. Here is where the
keyless magic comes from.
for_each over the service list read from
deployments/decl/<env>-defs.yaml — so adding a service to a
YAML file provisions its GSA and its Workload Identity binding. Declarative infrastructure,
driven by declarative data.
The other half — keyless CI (Course 5)
google_iam_workload_identity_pool (main.tf:6-13),
id gh-action-pool, with per-bot providers trusting GitHub's OIDC
issuer; the CI bot SAs (prod-build-bot, prod-deploy-bot,
integration-test-bot) live in
live/github-oidc/student-coach-e1e95/terragrunt.hcl, each bound to
a specific repo (principalSet://…/attribute.repository/<repo>). So the
"GitHub mints an OIDC token, GCP exchanges it" flow from Course 5 is configured here.
GCP — IAM + Workload Identity (GKE & Federation)
The member/role model, the KSA→GSA binding, and OIDC federation for external identities.
→ cloud.google.com — Workload Identity (GKE)
→ cloud.google.com — Workload Identity Federation
Check yourself (from memory)
Q1. The iam.gke.io/gcp-service-account annotation works because Terraform creates…
roles/iam.workloadIdentityUser, member
…svc.id.goog[ns/ksa]) grants the KSA permission to be the GSA. No binding = inert
annotation.
Q2. GKE Workload Identity vs Workload Identity Federation:
Q3. The per-service GSA + binding are created…
decl/<env>-defs.yaml
and generates a GSA + WI binding per service — data-driven.
google_service_account, google_project_iam_member). WORKLOAD IDENTITY
(GKE): a KSA impersonates a GSA, no static key. THE BINDING (crown jewel,
modules/apps/stag/sa_identity_namespaces.tf:41-54):
google_service_account_iam_member, role = "roles/iam.workloadIdentityUser",
member = serviceAccount:PROJECT.svc.id.goog[ns/ksa] — THIS is what makes the
iam.gke.io/gcp-service-account annotation (C3/C6) work; no binding = inert.
Generated for_each from decl/<env>-defs.yaml. WIF (external, C5):
google_iam_workload_identity_pool gh-action-pool + per-bot providers
(prod-build-bot…) trusting GitHub OIDC. Same idea, two contexts.1. GCP — IAM overview; in-repo modules/apps/stag/sa_identity_namespaces.tf:41-54.