Hedronite · Ops Lesson · 01-Earth-DevOps / Terraform · Wed 2026-09-23

Terraform GCP Cloud Run service — and run.invoker IAM

A Cloud Run URL is not public until something holds roles/run.invoker.

Lesson Class: Ops (DevOps + Terraform + GCP Cloud Run IAM)
Cloud Referent: google_cloud_run_v2_service + google_cloud_run_v2_service_iam_member (roles/run.invoker)
Paired Dev: Python providers schema JSON census
Paired Cert: Associate data sources, depends_on, read-only edges
Grounding: GCP-PCA-Notes Cloud Run · ace-pca Cloud Run row · Brikman referenced · Google provider docs
Service
Revisioned container + platform URL. Not a GCE VM.
run.invoker
IAM permission to invoke. Not project Editor.
Public
allUsers / allAuthenticatedUsers only when the ticket intends it.
Declare the service. Bind invokers on purpose. Keep public off by default.

<!-- hal:authoritative:yaml -->

*A Cloud Run URL is not a public API until something holds roles/run.invoker. Declare the service. Bind invokers on purpose.*

§I - Frame

09-20 filtered AWS ENI paths with separate security group rules. 09-17 wrote Azure passwords through write-only arguments. 09-14 imported a GCS bucket and moved its address. Those fires stay filed.

Today the cloud referent is GCP serverless HTTP. The concrete objects are google_cloud_run_v2_service plus google_cloud_run_v2_service_iam_member with role = "roles/run.invoker". Bootcamp PCA notes treat Cloud Run as container-shaped serverless HTTP, not a stateful VM substitute. The ACE/PCA cheatsheet maps it to serverless containers, HTTP-triggered.

PGA (08-30), Cloud SQL settings nests (08-24), and GKE Workload Identity (08-15) stay on their shelves. Tonight is service identity plus who may invoke.

§II - Three shelves

ShelfWhat it isWhat it is not
Cloud Run serviceRevisioned container listening on a port; URL minted by the platformA GCE VM you SSH into
roles/run.invokerIAM permission to invoke the service endpointEditor on the project, or "the URL is enough"
Public invokerExplicit member allUsers or allAuthenticatedUsers on run.invokerThe silent default of a fresh service you forgot to lock

Serverless VPC Access (PCA notes Private Service Stuffs adjacency) is how a Cloud Run revision reaches VPC resources without a public IP on the workload. Keep that connector for a later fire. Tonight the claim is invoke IAM, not private egress.

Artifact Registry holds image bits. The service references an image URI. Do not collapse "image exists" into "service is callable."

§III - Mechanism

resource "google_cloud_run_v2_service" "api" {
  name     = "api"
  location = var.region
  ingress  = "INGRESS_TRAFFIC_ALL"

  template {
    containers {
      image = "${var.region}-docker.pkg.dev/${var.project}/apps/api:1.0.0"
      ports {
        container_port = 8080
      }
    }
  }
}

# Intended callers only. Do not add allUsers unless the ticket says public.
resource "google_cloud_run_v2_service_iam_member" "ci_invoker" {
  project  = var.project
  location = google_cloud_run_v2_service.api.location
  name     = google_cloud_run_v2_service.api.name
  role     = "roles/run.invoker"
  member   = "serviceAccount:${var.ci_runner_sa}"
}

Fact one. Creating the service does not grant the world invoke rights. Unauthenticated callers get 403 until an invoker binding exists for them.

Fact two. allUsers plus roles/run.invoker is the public-HTTP claim. If that member is absent, treat the service as private-to-IAM even when ingress allows traffic to reach the frontend.

Fact three. Plan should show the IAM member as its own address. Binding CI (or a gateway SA) is a different row from binding allUsers. Reviewers read those rows separately on purpose.

Fact four. ingress controls where traffic may arrive (all / internal / internal-and-cloud-load-balancing). IAM controls who may successfully invoke after arrival. Mixing those two shelves is a common exam and PR trap.

§III.b - Public versus private in the plan

A reviewer reading plan output should be able to answer three questions without opening the Console:

  1. Does a google_cloud_run_v2_service address exist for the named service?
  2. Which IAM member addresses grant roles/run.invoker?
  3. Is allUsers or allAuthenticatedUsers among those members?

If question three is yes and the ticket did not ask for a public API, that is a ship-stop. If question three is no and the ticket asked for public HTTP, that is also a ship-stop. Public is intentional, not habitual.

When Maghrib later writes quiz items, prefer stems that separate ingress from invoker IAM. A service with INGRESS_TRAFFIC_ALL and no public invoker is still private-to-identity. A service with internal ingress and allUsers as invoker is a confused shelf pairing. Score the shelves separately.

For CI, prefer a dedicated runner service account as invoker over personal user principals. Rotate the SA without rewriting the service resource. That is why the IAM member is its own address.

Do not expand this fire into Serverless VPC Access connectors, Cloud Load Balancing serverless NEGs, or Artifact Registry repository IAM. Those are real GCP shelves and they are not tonight's claim graph. PCA notes already name Serverless VPC Access beside Cloud Run; leave that adjacency for a later GCP TF Ops visit.

§IV - Ops drill

  1. Declare a throwaway google_cloud_run_v2_service pointing at a known Artifact Registry image (or a Cloud Run hello sample URI in a lab project).
  2. Apply without any invoker member. Curl the service URL unauthenticated; expect deny.
  3. Add google_cloud_run_v2_service_iam_member for your user or a CI SA with roles/run.invoker. Re-invoke with identity; expect allow.
  4. Optional contrast only: temporarily bind allUsers as run.invoker, confirm public 200, then destroy that member in the same PR. Leave public off by default.
  5. Read plan: service resource and IAM member are distinct addresses. Confirm no leftover public member after cleanup.

Success criteria: unauthenticated deny without invoker; authenticated allow with the intended member; no leftover allUsers binding unless the ticket documents a public API; plan names both addresses.

§V - Close instruction

Apply the drill in a lab project. Paste the plan summary that names both the service and the invoker member. Pair: Dev censuses terraform providers schema -json for Google provider attribute inventory; Cert names data sources and the read-only edge (depends_on discipline) on Associate objectives. Maghrib owns quiz.html later.

Related