@caelo-cms/provisioning 0.3.18 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/provisioning",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
@@ -38,7 +38,7 @@
38
38
  "prepublishOnly": "tsc -b --force && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
39
39
  },
40
40
  "dependencies": {
41
- "@caelo-cms/shared": "^0.3.18",
41
+ "@caelo-cms/shared": "^0.3.19",
42
42
  "@clack/prompts": "1.3.0",
43
43
  "@pulumi/aws": "^7",
44
44
  "@pulumi/awsx": "^3",
@@ -546,22 +546,55 @@ const adminDomainMapping = provisionAdminDomain
546
546
  // shipped. If your provider build is older than v8.x, run
547
547
  // `gcloud beta run services update <admin-service> --iap` as a
548
548
  // post-up step. The stack provisions the IAM bindings either way.
549
- // v0.3.17 — Native IAP on Cloud Run requires TWO bindings per
550
- // allowlisted user (testing showed iap-only isn't enough — Cloud
551
- // Run still rejects with 403 Forbidden after IAP forwards):
549
+ // v0.3.19 — Native IAP on Cloud Run needs THREE bindings to
550
+ // actually serve requests. The first two go per allowlisted user;
551
+ // the third goes on the IAP service identity (one for the whole
552
+ // install).
552
553
  //
553
- // 1. `roles/iap.httpsResourceAccessor` on the IAP WEB layer —
554
- // lets the user pass IAP authentication.
554
+ // 1. `roles/iap.httpsResourceAccessor` on the IAP WEB layer
555
+ // (per user) — passes IAP auth.
555
556
  // Resource: `gcp.iap.WebCloudRunServiceIamMember`
556
557
  // (matches `gcloud beta iap web add-iam-policy-binding`).
557
558
  //
558
- // 2. `roles/run.invoker` on the Cloud Run service v2 IAM —
559
- // lets Cloud Run accept the IAP-forwarded request. Without
560
- // this, Cloud Run's own auth gate rejects with "Your client
561
- // does not have permission".
559
+ // 2. `roles/run.invoker` on the Cloud Run service v2 IAM
560
+ // (per user) — kept for "private mode" (user hits the
561
+ // *.run.app URL with an OAuth token directly, bypassing
562
+ // IAP). Not strictly required for IAP-forwarded traffic
563
+ // since IAP forwards as its own SA (#3) — but the user
564
+ // principal is harmless extra.
562
565
  //
563
- // Both are needed for IAP-protected Cloud Run to actually serve
564
- // requests to the operator.
566
+ // 3. `roles/run.invoker` to the IAP SERVICE IDENTITY
567
+ // (`service-<projectnum>@gcp-sa-iap.iam.gserviceaccount.com`)
568
+ // on the Cloud Run service. THIS is the critical one for
569
+ // IAP-forwarded traffic: when IAP intercepts a request and
570
+ // forwards to Cloud Run, it does so AS the IAP SA, and
571
+ // Cloud Run gates the invocation on `run.invoker` for that
572
+ // principal. Without this, Cloud Run returns 403 "request
573
+ // was not authenticated" even after IAP allowed the user
574
+ // through.
575
+ //
576
+ // The IAP SA is materialized via `gcp.projects.ServiceIdentity`
577
+ // (works for iap.googleapis.com; the v0.3.10 attempt failed only
578
+ // for firebasehosting.googleapis.com).
579
+
580
+ const iapServiceIdentity = new gcp.projects.ServiceIdentity(
581
+ `${namePrefix}-iap-sa`,
582
+ { project, service: "iap.googleapis.com" },
583
+ opts,
584
+ );
585
+
586
+ new gcp.cloudrunv2.ServiceIamMember(
587
+ `${namePrefix}-admin-iap-sa-invoker`,
588
+ {
589
+ project,
590
+ location: region,
591
+ name: adminSvc.name,
592
+ role: "roles/run.invoker",
593
+ member: pulumi.interpolate`serviceAccount:${iapServiceIdentity.email}`,
594
+ },
595
+ { ...opts, dependsOn: [iapServiceIdentity] },
596
+ );
597
+
565
598
  for (const principal of iapAllowlist) {
566
599
  const slug = principal.replace(/[^a-z0-9]/gi, "-").slice(0, 40);
567
600