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.

Anchor — the binding (the crown jewel of this course) deployments/terraform/modules/apps/stag/sa_identity_namespaces.tf:41-54:
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.
Data-driven — one binding per service, from YAML You don't write one of these per service by hand. The GSA + binding are generated 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)

Anchor — the GitHub WIF pool + bots Course 5's keyless CI auth is the external variant, also Terraform. The modules/github-oidc module creates a 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.
Two flavours of Workload Identity, one idea GKE Workload Identity (KSA → GSA) for pods; Workload Identity Federation (GitHub OIDC → GSA) for CI. Same principle — federate an external identity to a GCP SA, no static keys — two contexts. Both are IAM bindings in Terraform. That's the single most-reused idea across the whole track, and it all lives in this module tree.
Read this next

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…

The binding (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:

Same idea (federate an external identity to a GCP SA, no keys), two contexts: pods (GKE WI) and CI (WIF, `gh-action-pool`).

Q3. The per-service GSA + binding are created…

The apps module reads decl/<env>-defs.yaml and generates a GSA + WI binding per service — data-driven.
IAM + Workload Identity — the binding behind six courses of keyless auth.
recall, then click to reveal
GCP IAM = MEMBER (user/group/service account) granted a ROLE on a resource (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.
Want to see which GCP roles a specific service's GSA gets, or how the WIF attribute condition restricts a bot to one repo/branch? Ask me.

1. GCP — IAM overview; in-repo modules/apps/stag/sa_identity_namespaces.tf:41-54.