From b9c5a6d37e3a23c42f3ee3b1f1094cfc483a067c Mon Sep 17 00:00:00 2001 From: phenrickson Date: Thu, 16 Jul 2026 13:49:32 -0500 Subject: [PATCH 1/2] feat(api): gate bgg-warehouse-api via authoritative Terraform invoker binding (7b) Authoritative google_cloud_run_v2_service_iam_binding on run.invoker guarantees no allUsers. Day-one member is the owner; consumers join the invoker group. Applied by terraform.yml. MUST merge AFTER the service exists (deploy PR first). Co-Authored-By: Claude Opus 4.8 (1M context) --- terraform/warehouse_api.tf | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 terraform/warehouse_api.tf diff --git a/terraform/warehouse_api.tf b/terraform/warehouse_api.tf new file mode 100644 index 0000000..1f21c70 --- /dev/null +++ b/terraform/warehouse_api.tf @@ -0,0 +1,32 @@ +# Gating for the warehouse read API (bgg-warehouse-api Cloud Run service). +# +# The service itself is deployed by the Cloud Build Actions workflow +# (config/cloudbuild.warehouse-api.yaml). Terraform owns ONLY its inbound invoker IAM, +# as an AUTHORITATIVE binding so `allUsers` can never be (re)added out of band — the +# whole point of the gating. Applied by .github/workflows/terraform.yml. +# +# ORDERING: this binding targets an existing service, so it must be applied AFTER the +# service is first deployed (merge the deploy PR before merging this one). +# +# See docs/superpowers/specs/2026-07-16-service-auth-pattern-design.md + +variable "warehouse_api_invoker_members" { + description = <<-EOT + Principals granted roles/run.invoker on bgg-warehouse-api (AUTHORITATIVE — this is + the complete allow-list; anything not here, including allUsers, cannot invoke). + Consumer-agnostic: prefer adding a new consumer's SA to the invoker Google Group + rather than listing it here. Add the group once it exists in Workspace, e.g. + "group:bgg-api-invokers@googlegroups.com". + EOT + type = list(string) + default = ["user:phil.henrickson@gmail.com"] +} + +resource "google_cloud_run_v2_service_iam_binding" "warehouse_api_invokers" { + project = var.project_id + location = var.region + name = "bgg-warehouse-api" + role = "roles/run.invoker" + + members = var.warehouse_api_invoker_members +} From f16bf49720ee4ceb4b2de9a93b24e27ccff143a3 Mon Sep 17 00:00:00 2001 From: phenrickson Date: Thu, 16 Jul 2026 14:07:16 -0500 Subject: [PATCH 2/2] docs(tf): members list is the grant surface; list identities directly (not a group) Co-Authored-By: Claude Opus 4.8 (1M context) --- terraform/warehouse_api.tf | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/terraform/warehouse_api.tf b/terraform/warehouse_api.tf index 1f21c70..5dcf530 100644 --- a/terraform/warehouse_api.tf +++ b/terraform/warehouse_api.tf @@ -14,9 +14,13 @@ variable "warehouse_api_invoker_members" { description = <<-EOT Principals granted roles/run.invoker on bgg-warehouse-api (AUTHORITATIVE — this is the complete allow-list; anything not here, including allUsers, cannot invoke). - Consumer-agnostic: prefer adding a new consumer's SA to the invoker Google Group - rather than listing it here. Add the group once it exists in Workspace, e.g. - "group:bgg-api-invokers@googlegroups.com". + + This list IS the grant surface: to give a consumer access, add its identity here and + merge (terraform.yml applies it) — grants stay in code, reviewed, and git-audited. + - a person: "user:someone@example.com" + - a service: "serviceAccount:new-frontend@bgg-data-warehouse.iam.gserviceaccount.com" + (A "group:..." member is possible too, but a group's membership is managed outside + Terraform/Actions — prefer listing identities directly here.) EOT type = list(string) default = ["user:phil.henrickson@gmail.com"]