From fc23e472738acb61b3d26354be9fa79a038f8262 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 7 Oct 2026 14:56:14 +0200 Subject: [PATCH 1/3] docs(cli): add legacy Compute Admin migration checklist to GCP setup Older configure-gcp wizards granted roles/compute.admin and rerunning the current wizard preserves that binding. Document the operator checklist for the inventory-and-remediation part of #2123: inventory broad grants with owner authorization, establish the narrow roles, validate them live without purchasing, and revoke only after explicit approval. --- docs/cli/cloud-setup.md | 59 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/docs/cli/cloud-setup.md b/docs/cli/cloud-setup.md index 3c26212c1..44d7fcb74 100644 --- a/docs/cli/cloud-setup.md +++ b/docs/cli/cloud-setup.md @@ -147,8 +147,63 @@ the wizard refuses incompatible roles rather than changing them. It also refuses to widen an existing conditional-only grant for this Service Account. Rerunning the wizard does not remove broad grants from older installations. -Review existing `roles/compute.admin` grants separately. Recommendation access -requires additional Recommender permissions; neither role above provides them. +Recommendation access requires additional Recommender permissions; neither +role above provides them. + +### Migrating legacy Compute Admin grants + +Earlier setup wizard versions granted +`roles/compute.admin` to the `cudly-service-account` Service Account before +minting its key. Rerunning the current wizard adds the narrow roles but +preserves existing bindings, so every older installation needs a one-time +operator review. Work through this checklist with the project owner's +authorization: + +1. **Inventory broad grants.** List the project IAM policy and identify CUDly + identities that still hold Compute Admin, directly or inherited: + + ```bash + gcloud projects get-iam-policy PROJECT_ID \ + --flatten=bindings[].members \ + --filter=bindings.role:roles/compute.admin \ + --format='table(bindings.role, bindings.members)' + ``` + + Record every `cudly-service-account@...` match, including bindings inherited + from folder or organization policies. Do not change live IAM during the + inventory. + +2. **Establish the narrow permissions.** Rerun `cudly configure-gcp` (or grant + manually) so the Service Account holds `roles/compute.viewer` and + `projects/PROJECT_ID/roles/cudlyCommitmentPurchaser` as described above. An + existing custom role must contain exactly `compute.commitments.create` and + be enabled; the wizard refuses incompatible roles rather than changing them. + +3. **Verify without purchasing.** Authenticate as the Service Account and + confirm the replacement role is effective on the live project: + + ```bash + gcloud auth activate-service-account --key-file=~/cudly-gcp-key.json + gcloud projects test-iam-permissions PROJECT_ID \ + --permissions=compute.commitments.create,compute.commitments.list + ``` + + Then run the read-only analysis workflow and confirm it completes without + permission errors. This live validation is required: local HTTP fixtures + only prove SDK request behaviour, not IAM propagation. + +4. **Revoke with approval.** Only after the owner approves the inventory + result, remove the broad binding while leaving unrelated access and IAM + conditions intact: + + ```bash + gcloud projects remove-iam-policy-binding PROJECT_ID \ + --member=serviceAccount:cudly-service-account@PROJECT_ID.iam.gserviceaccount.com \ + --role=roles/compute.admin + ``` + + Record the inventory result and any deployment-specific follow-up. Never + delete bindings or rotate keys without per-resource authorization. If you manage Cloud SQL or Memorystore commitments, you may need additional roles. Check the GCP documentation for the minimum required permissions per commitment type. From 82cdc92a95fbe1f91fb315ebea8141c494dc0498 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 7 Oct 2026 15:03:11 +0200 Subject: [PATCH 2/3] docs(cli): harden legacy Compute Admin checklist after review Address independent review on #2137: cover inherited folder/org bindings with get-ancestors-iam-policy, note that test-iam-permissions silently omits missing permissions, restore the operator identity before the revocation step, flag the --condition failure mode on conditional bindings, and cover installs whose local key file was already removed. --- docs/cli/cloud-setup.md | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/docs/cli/cloud-setup.md b/docs/cli/cloud-setup.md index 44d7fcb74..c138bd227 100644 --- a/docs/cli/cloud-setup.md +++ b/docs/cli/cloud-setup.md @@ -160,7 +160,7 @@ operator review. Work through this checklist with the project owner's authorization: 1. **Inventory broad grants.** List the project IAM policy and identify CUDly - identities that still hold Compute Admin, directly or inherited: + identities that still hold Compute Admin: ```bash gcloud projects get-iam-policy PROJECT_ID \ @@ -169,9 +169,18 @@ authorization: --format='table(bindings.role, bindings.members)' ``` - Record every `cudly-service-account@...` match, including bindings inherited - from folder or organization policies. Do not change live IAM during the - inventory. + The project policy does not include inherited bindings, so check folder and + organization policies too: + + ```bash + gcloud projects get-ancestors-iam-policy PROJECT_ID \ + --flatten=bindings[].members \ + --filter=bindings.role:roles/compute.admin \ + --format='table(bindings.role, bindings.members)' + ``` + + Record every `cudly-service-account@...` match. Do not change live IAM + during the inventory. 2. **Establish the narrow permissions.** Rerun `cudly configure-gcp` (or grant manually) so the Service Account holds `roles/compute.viewer` and @@ -179,8 +188,10 @@ authorization: existing custom role must contain exactly `compute.commitments.create` and be enabled; the wizard refuses incompatible roles rather than changing them. -3. **Verify without purchasing.** Authenticate as the Service Account and - confirm the replacement role is effective on the live project: +3. **Verify without purchasing.** Authenticate as the Service Account (use + the key file path from your installation; mint a fresh key if the local + copy is gone) and confirm the replacement roles are effective on the live + project: ```bash gcloud auth activate-service-account --key-file=~/cudly-gcp-key.json @@ -188,9 +199,17 @@ authorization: --permissions=compute.commitments.create,compute.commitments.list ``` - Then run the read-only analysis workflow and confirm it completes without - permission errors. This live validation is required: local HTTP fixtures - only prove SDK request behaviour, not IAM propagation. + The command silently omits permissions the caller lacks, so confirm both + appear in the output. Then run the read-only analysis workflow and confirm + it completes without permission errors. This live validation is required: + local HTTP fixtures only prove SDK request behaviour, not IAM propagation. + + Switch back to your own identity before continuing; the remaining steps + need your operator permissions: + + ```bash + gcloud auth login + ``` 4. **Revoke with approval.** Only after the owner approves the inventory result, remove the broad binding while leaving unrelated access and IAM @@ -202,6 +221,10 @@ authorization: --role=roles/compute.admin ``` + If the binding carries an IAM condition, the removal command fails until + you pass a matching `--condition` flag; treat that as a cue to review the + condition with the owner, not to force the removal. + Record the inventory result and any deployment-specific follow-up. Never delete bindings or rotate keys without per-resource authorization. From 38124c788dc4b84689d05a2ad05dc86f1a071be3 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 7 Oct 2026 22:05:10 +0200 Subject: [PATCH 3/3] docs(cli): correct legacy grant inventory and verification Use ancestor policy field paths and the supported IAM permissions API. Inspect replacement bindings before approved removal, then verify access after broad grants stop masking missing narrow permissions. Refs #2123 --- docs/cli/cloud-setup.md | 102 ++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 29 deletions(-) diff --git a/docs/cli/cloud-setup.md b/docs/cli/cloud-setup.md index c138bd227..d0200e13b 100644 --- a/docs/cli/cloud-setup.md +++ b/docs/cli/cloud-setup.md @@ -166,7 +166,7 @@ authorization: gcloud projects get-iam-policy PROJECT_ID \ --flatten=bindings[].members \ --filter=bindings.role:roles/compute.admin \ - --format='table(bindings.role, bindings.members)' + --format='table(bindings.role, bindings.members, bindings.condition)' ``` The project policy does not include inherited bindings, so check folder and @@ -174,13 +174,15 @@ authorization: ```bash gcloud projects get-ancestors-iam-policy PROJECT_ID \ - --flatten=bindings[].members \ - --filter=bindings.role:roles/compute.admin \ - --format='table(bindings.role, bindings.members)' + --flatten=policy.bindings[].members \ + --filter=policy.bindings.role:roles/compute.admin \ + --format='table(type, id, policy.bindings.role, policy.bindings.members, policy.bindings.condition)' ``` - Record every `cudly-service-account@...` match. Do not change live IAM - during the inventory. + Also save the full JSON output of both commands with `--format=json` and + without `--flatten` or `--filter`, so all bindings and conditions remain + available for review. Record each exact Service Account email, resource + type and ID, role, and condition. Do not change live IAM during inventory. 2. **Establish the narrow permissions.** Rerun `cudly configure-gcp` (or grant manually) so the Service Account holds `roles/compute.viewer` and @@ -188,46 +190,88 @@ authorization: existing custom role must contain exactly `compute.commitments.create` and be enabled; the wizard refuses incompatible roles rather than changing them. -3. **Verify without purchasing.** Authenticate as the Service Account (use - the key file path from your installation; mint a fresh key if the local - copy is gone) and confirm the replacement roles are effective on the live - project: + Before removing anything, inspect the full project policy for both exact + role names and the exact Service Account member, including conditions: ```bash - gcloud auth activate-service-account --key-file=~/cudly-gcp-key.json - gcloud projects test-iam-permissions PROJECT_ID \ - --permissions=compute.commitments.create,compute.commitments.list + gcloud projects get-iam-policy PROJECT_ID --format=json + gcloud iam roles describe cudlyCommitmentPurchaser \ + --project=PROJECT_ID --format=json ``` - The command silently omits permissions the caller lacks, so confirm both - appear in the output. Then run the read-only analysis workflow and confirm - it completes without permission errors. This live validation is required: - local HTTP fixtures only prove SDK request behaviour, not IAM propagation. + Confirm the custom role is not deleted, its stage is not `DISABLED`, and + `includedPermissions` contains only `compute.commitments.create`. Confirm + the replacement bindings' conditions permit the intended workflow. A + successful permission check while Compute Admin remains granted cannot + prove the narrow roles are sufficient: the broad grant masks missing access. - Switch back to your own identity before continuing; the remaining steps - need your operator permissions: +3. **Revoke each approved binding at its recorded scope.** Obtain owner + authorization for each exact resource, member, role, and condition. Record + the approved rollback (restoring that exact binding and condition) before + removal. As the operator, use the command matching the resource that owns + the binding. These examples remove only unconditional grants: ```bash - gcloud auth login + gcloud projects remove-iam-policy-binding PROJECT_ID \ + --member=serviceAccount:cudly-service-account@PROJECT_ID.iam.gserviceaccount.com \ + --role=roles/compute.admin --condition=None + gcloud resource-manager folders remove-iam-policy-binding FOLDER_ID \ + --member=serviceAccount:cudly-service-account@PROJECT_ID.iam.gserviceaccount.com \ + --role=roles/compute.admin --condition=None + gcloud organizations remove-iam-policy-binding ORGANIZATION_ID \ + --member=serviceAccount:cudly-service-account@PROJECT_ID.iam.gserviceaccount.com \ + --role=roles/compute.admin --condition=None ``` -4. **Revoke with approval.** Only after the owner approves the inventory - result, remove the broad binding while leaving unrelated access and IAM - conditions intact: + Substitute the inventoried Service Account email, which may belong to a + different project. For a conditional binding, replace `--condition=None` + with the exact owner-approved condition using `--condition` or + `--condition-from-file`; preserve its expression, title, and description. + Do not use `--all` or remove other bindings. Recheck the owning policy after + each removal and confirm only the authorized binding changed. + +4. **Verify after removal without purchasing.** Authenticate with the + installation's existing Service Account key. A missing key requires separate + authorization for credential recovery, not automatic key creation. Obtain + a token as that Service Account and call Cloud Resource Manager's + `projects.testIamPermissions` REST endpoint: + + Before authenticating, ensure the `auth/impersonate_service_account` gcloud + configuration property and `CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT` + environment variable are unset. If either is set, stop and select an + owner-approved configuration and environment without impersonation; do not + automatically change existing settings. Substitute the inventoried Service + Account email in the token command below. ```bash - gcloud projects remove-iam-policy-binding PROJECT_ID \ - --member=serviceAccount:cudly-service-account@PROJECT_ID.iam.gserviceaccount.com \ - --role=roles/compute.admin + gcloud auth activate-service-account --key-file=~/cudly-gcp-key.json + CUDLY_GCP_TOKEN="$(gcloud auth print-access-token --account=cudly-service-account@PROJECT_ID.iam.gserviceaccount.com)" + curl --fail-with-body --request POST \ + "https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID:testIamPermissions" \ + --header "Authorization: Bearer ${CUDLY_GCP_TOKEN}" \ + --header 'Content-Type: application/json' \ + --data '{"permissions":["compute.commitments.create","compute.commitments.list"]}' + unset CUDLY_GCP_TOKEN ``` - If the binding carries an IAM condition, the removal command fails until - you pass a matching `--condition` flag; treat that as a cue to review the - condition with the owner, not to force the removal. + The response omits permissions the caller lacks. Confirm both requested + permissions appear, then run the installation's read-only CUDly analysis + workflow with the same credentials and project and confirm it completes + without permission errors. Do not purchase a commitment as a verification + step. If validation fails, stop, switch back to the authorized operator + identity with `gcloud auth login`, then follow the authorized rollback; do + not broaden roles without owner approval. Also switch back to your operator + identity before any further operator actions after successful validation. Record the inventory result and any deployment-specific follow-up. Never delete bindings or rotate keys without per-resource authorization. + This documentation change was verified with offline fixtures and a local + HTTP endpoint, not a live cloud account. Those checks cover command data + shape and request construction; they do not prove deployment-specific IAM + propagation or the live read-only workflow. Record those coverage gaps + honestly when reviewing or applying the migration. + If you manage Cloud SQL or Memorystore commitments, you may need additional roles. Check the GCP documentation for the minimum required permissions per commitment type. ### Credentials file format