Setup guide
Everything you must provision before this skill can run โ the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.
You need an existing Google Cloud organization node (a verified Cloud Identity or Workspace domain), org-level IAM roles on the caller, an open billing account, and a hand-created 'seed' project holding the Terraform state bucket and a Workload Identity Federation pool for CI โ the skill refuses to guess org IDs, billing IDs, or CIDRs and org policy blocks service account keys, so CI must be federated rather than key-based.
You need to already have
- A Google Cloud organization node already exists โ verify with `gcloud organizations list`. If projects sit under 'No organization', you must first sign up for Cloud Identity Free or Workspace, verify the DNS domain, and migrate projects; that is separate work the skill will not fake.
- The caller is an Organization Administrator (roles/resourcemanager.organizationAdmin) on the org node, or can get an org admin to grant the role set below. On a brand-new org the initial Cloud Identity super admin must claim roles/resourcemanager.organizationAdmin at Cloud Console > IAM & Admin > IAM with the organization selected in the resource picker.
- An open, non-closed Cloud Billing account the caller can link projects to (roles/billing.admin on the billing account, or roles/billing.user plus roles/resourcemanager.projectBillingManager).
- Google Cloud CLI 480.0.0 or newer installed and authenticated (`gcloud auth login` and `gcloud auth application-default login`), with an ADC quota project set โ org-level APIs bill quota to a project.
- Terraform >= 1.9 with the hashicorp/google provider pinned to ~> 6.0.
- Google Groups already created in Cloud Identity for every role binding (the skill binds groups only, never individual users) โ and created BEFORE iam.allowedPolicyMemberDomains is enforced, or you lock out your own automation.
- A hand-created seed/bootstrap project (Terraform cannot create its own state backend) linked to billing, with a versioned GCS state bucket using uniform bucket-level access and public access prevention.
- Cloud Identity customer ID on hand for the iam.allowedPolicyMemberDomains constraint: `gcloud organizations describe ORG_ID --format='value(owner.directoryCustomerId)'`.
- Approved region list, Google Group naming scheme, non-overlapping on-prem/peer CIDRs from the network team, and the monthly budget target from finance.
How the pieces connect
Credentials this skill needs
Set these as environment variables. Never paste secrets into a chat or commit them.
| Variable | What it is | Where to get it |
|---|---|---|
ORG_ID | Google Cloud organization ID format: 12-digit numeric string, e.g. 123456789012 (no 'organizations/' prefix) | Run `gcloud organizations list` and read the ID column, or Cloud Console > IAM & Admin > Settings with the organization selected in the top resource picker (Organization ID field). |
BILLING_ID | Cloud Billing account ID format: XXXXXX-XXXXXX-XXXXXX (three 6-char uppercase hex groups) | Run `gcloud billing accounts list` and read ACCOUNT_ID, or Cloud Console > Billing > Manage billing accounts > click the account > the ID is in the header and in the URL as ?billingAccount=. |
SEED | Seed / bootstrap project ID format: 6-30 char lowercase project ID, e.g. acme-seed-prod (project ID, not project number, not display name) | You create it: Cloud Console > IAM & Admin > Manage Resources > CREATE PROJECT, parent = the organization. Or `gcloud projects create "$SEED" --organization="$ORG_ID"`. This project holds Terraform state, the WIF pool, and nothing else. |
REGION | Default region for the state bucket and initial subnets format: GCP region string, e.g. us-central1 โ must be inside whatever gcp.resourceLocations will allow | Pick from Cloud Console > Compute Engine > VM instances > Create (Region dropdown), or `gcloud compute regions list`. Must match the approved-regions list from the business. |
HOSToptional | Shared VPC host project ID for the environment being wired format: lowercase project ID, e.g. acme-net-host-prod | Created by the skill's Terraform (google_project.net_host); read back with `gcloud compute shared-vpc organizations list-host-projects "$ORG_ID"` or Cloud Console > VPC network > Shared VPC. |
GOOGLE_APPLICATION_CREDENTIALSsecretoptional | Path to the Workload Identity Federation credential configuration file (CI only) format: Absolute filesystem path to an external_account JSON config produced by `gcloud iam workload-identity-pools create-cred-config` โ NOT a service account key (org policy iam.disableServiceAccountKeyCreation blocks those) | Generate with `gcloud iam workload-identity-pools create-cred-config projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/ci/providers/github --service-account=RUNNER_SA_EMAIL --output-file=cred.json --credential-source-file=$GITHUB_OIDC_TOKEN_FILE`. Pool and provider live at Cloud Console > IAM & Admin > Workload Identity Federation. Leave unset when running interactively โ `gcloud auth application-default login` is enough. |
TF_VAR_org_id | Terraform input: organization ID format: Same 12-digit value as ORG_ID; Terraform reads variable "org_id" from this env var | Set from ORG_ID in your shell or CI: `export TF_VAR_org_id="$ORG_ID"`. |
TF_VAR_billing_account | Terraform input: billing account ID format: Same XXXXXX-XXXXXX-XXXXXX value as BILLING_ID; feeds google_project.billing_account | Set from BILLING_ID: `export TF_VAR_billing_account="$BILLING_ID"`. |
TF_VAR_seed_project_id | Terraform input: seed project ID format: Same lowercase project ID as SEED; used as provider billing_project (quota project) and as the BigQuery log sink destination project | Set from SEED: `export TF_VAR_seed_project_id="$SEED"`. |
Permissions to grant
roles/resourcemanager.organizationAdminIAM roleadmin consent requiredRead and set IAM policy on the organization node, and create the org-level bindings the landing zone depends on.
โณ For read-only discovery (`gcloud organizations get-iam-policy`, asset searches) roles/resourcemanager.organizationViewer plus roles/iam.securityReviewer is enough; only the apply phase needs organizationAdmin.
roles/resourcemanager.folderCreatorIAM roleadmin consent requiredCreate the /prod, /nonprod, /common, /bootstrap folder hierarchy at the org node.
โณ roles/resourcemanager.folderAdmin also grants delete and setIamPolicy on folders; folderCreator is the narrower choice for a pure build-out.
roles/resourcemanager.projectCreatorIAM roleadmin consent requiredCreate the seed project, the per-environment Shared VPC host projects, and every workload service project.
โณ Grant at the folder rather than the organization once the hierarchy exists, so the CI identity cannot create projects outside its own folder.
roles/billing.adminIAM roleadmin consent requiredLink new projects to the billing account, turn on the BigQuery billing export, and create budgets.
โณ Split it: roles/billing.user on the billing account plus roles/resourcemanager.projectBillingManager on the folder covers linking, and roles/billing.costsManager covers budget create/update without full admin.
roles/orgpolicy.policyAdminIAM roleadmin consent requiredSet and dry-run the org policy baseline (iam.disableServiceAccountKeyCreation, compute.vmExternalIpAccess, gcp.resourceLocations, storage.publicAccessPrevention, and the rest).
โณ Simulation and read-back only need roles/orgpolicy.policyViewer plus roles/policysimulator.admin; hold policyAdmin only for the enforcing apply.
roles/logging.configWriterIAM roleadmin consent requiredCreate the aggregated organization sink with include_children=true and its exclusion filters.
โณ roles/logging.viewer suffices to audit existing sinks; configWriter is only needed for create/update.
roles/compute.xpnAdminIAM roleadmin consent requiredEnable each environment's project as a Shared VPC host and attach service projects to it.
โณ Must be granted at the organization or folder โ it cannot be granted at project scope. Once hosts exist, day-2 subnet grants only need roles/compute.networkAdmin on the host project.
roles/iam.organizationRoleAdminIAM roleadmin consent requiredDefine org-level custom roles for the few cases where the closest predefined role carries roughly twice the permissions needed.
โณ Skip entirely if you can live with predefined roles โ the skill's own guidance is to prefer predefined roles.
roles/iam.denyAdminIAM roleadmin consent requiredCreate the org-node IAM deny policy that backstops service-account-key creation, since deny is evaluated before allow and no project-level grant can override it.
โณ roles/iam.denyReviewer gives read-only visibility into existing deny policies for audit work.
roles/iam.workloadIdentityPoolAdminIAM roleCreate the CI workload identity pool and the GitHub OIDC provider with its mandatory attribute condition, so CI never needs a service account key.
โณ Grant on the seed project only, never at the org node.
roles/serviceusage.serviceUsageAdminIAM roleBatch-enable cloudresourcemanager, cloudbilling, iam, orgpolicy, compute, logging, storage, cloudasset, and billingbudgets APIs on the seed and host projects.
โณ roles/serviceusage.serviceUsageConsumer is enough at runtime once the APIs are on.
roles/storage.adminIAM roleCreate the versioned Terraform state bucket with uniform bucket-level access and public access prevention.
โณ Grant on the seed project only. After creation, the CI runner needs just roles/storage.objectAdmin on that single bucket to read and write state.
roles/cloudasset.viewerIAM roleadmin consent requiredExport the org-wide resource baseline and run `searchAllIamPolicies` to hunt residual roles/owner bindings.
โณ Grant at the org node for full-estate visibility, or at a single folder if the review is scoped to one environment.
roles/recommender.iamViewerIAM roleRead IAM policy recommendations after 60-90 days of real traffic to trim over-broad bindings.
โณ roles/recommender.iamAdmin is only needed if you want to mark recommendations as claimed/succeeded from automation.
roles/bigquery.dataEditorIAM roleGranted to the log sink's returned writer_identity on the destination dataset โ without it the sink silently drops every record.
โณ Grant on the single destination dataset via google_bigquery_dataset_iam_member, never at project scope. For a GCS destination the equivalent is roles/storage.objectCreator on the one bucket.
roles/compute.networkUserIAM roleGranted to each workload's Google Group so service-project VMs can attach to the Shared VPC subnet.
โณ Always grant at SUBNET scope (`gcloud compute networks subnets add-iam-policy-binding`), never at host-project scope โ project scope hands the team every subnet in the VPC.
https://www.googleapis.com/auth/cloud-platformAPI scopeThe OAuth scope gcloud and the Terraform google provider request for every Resource Manager, Org Policy, Billing, Compute, Logging, and Asset Inventory call in this skill.
โณ https://www.googleapis.com/auth/cloud-platform.read-only covers the discovery and audit phases (org list, asset search, policy read-back) but not any create or set operation.
Step by step
- 1
Confirm an organization node actually exists
Run `gcloud organizations list`. If it returns nothing, or `gcloud projects list --format='table(projectId,parent.id,parent.type)'` shows projects with no parent, stop: you need a verified Cloud Identity or Workspace domain first, then a project migration. Record the 12-digit ID as ORG_ID and grab the Cloud Identity customer ID with `gcloud organizations describe "$ORG_ID" --format='value(owner.directoryCustomerId)'` โ the iam.allowedPolicyMemberDomains constraint needs it.
Open in the console / docs โ - 2
Grant the caller the org-level role set
In Cloud Console, switch the resource picker at the top of the page to the ORGANIZATION (not a project), go to IAM & Admin > IAM, click GRANT ACCESS, and add the caller's Google Group as principal with: Organization Administrator, Folder Creator, Project Creator, Organization Policy Administrator, Logs Configuration Writer, Compute Shared VPC Admin, Organization Role Administrator, Deny Admin, Cloud Asset Viewer. Only a Cloud Identity super admin can bootstrap the first Organization Administrator grant. Verify with `gcloud organizations get-iam-policy "$ORG_ID" --flatten='bindings[]' --filter='bindings.members:YOUR_PRINCIPAL' --format='value(bindings.role)'` and stop rather than half-applying if roles are missing.
Open in the console / docs โ - 3
Grant billing access and note the billing account ID
Cloud Console > Billing > Manage billing accounts > select the account > ACCOUNT MANAGEMENT tab > ADD PRINCIPAL, and grant Billing Account Administrator (or Billing Account User + Project Billing Manager on the folder, plus Billing Account Costs Manager for budgets). Copy the ACCOUNT_ID from `gcloud billing accounts list` into BILLING_ID and confirm OPEN is True โ a closed account fails every project link.
Open in the console / docs โ - 4
Create the seed project by hand and enable APIs
Terraform cannot create its own state backend, so this one project is manual: `gcloud projects create "$SEED" --organization="$ORG_ID"`, then `gcloud billing projects link "$SEED" --billing-account="$BILLING_ID"`, then `gcloud services enable cloudresourcemanager.googleapis.com cloudbilling.googleapis.com iam.googleapis.com orgpolicy.googleapis.com serviceusage.googleapis.com compute.googleapis.com logging.googleapis.com storage.googleapis.com cloudasset.googleapis.com billingbudgets.googleapis.com --project="$SEED"`. Also set the ADC quota project: `gcloud auth application-default set-quota-project "$SEED"`.
Open in the console / docs โ - 5
Create the versioned, locked-down Terraform state bucket
`gcloud storage buckets create "gs://${SEED}-tfstate" --project="$SEED" --location="$REGION" --uniform-bucket-level-access --public-access-prevention` then `gcloud storage buckets update "gs://${SEED}-tfstate" --versioning`. Use one bucket with a separate `prefix` per Terraform layer (bootstrap / org / network / projects) so one bad apply cannot take out the hierarchy.
Open in the console / docs โ - 6
Federate CI with Workload Identity Federation instead of a key
Create the pool and the GitHub OIDC provider on the seed project, then bind the runner service account. The `--attribute-condition` is MANDATORY when you map `assertion.repository` โ without it any repository on token.actions.githubusercontent.com can assume the identity: `gcloud iam workload-identity-pools create ci --location=global --project="$SEED"` then `gcloud iam workload-identity-pools providers create-oidc github --workload-identity-pool=ci --location=global --project="$SEED" --issuer-uri="https://token.actions.githubusercontent.com" --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" --attribute-condition="assertion.repository=='ACME/gcp-foundation'"`, then grant the runner SA roles/iam.workloadIdentityUser for principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/ci/attribute.repository/ACME/gcp-foundation. Browse the result at IAM & Admin > Workload Identity Federation.
Open in the console / docs โ - 7
Point the Terraform google provider at a quota project
Org-level API calls (Resource Manager v3, Org Policy v2, Cloud Asset) fail with quota-project errors unless the provider block sets `billing_project = var.seed_project_id` and `user_project_override = true`, with the provider pinned `version = "~> 6.0"` โ field names move across majors. Export TF_VAR_org_id, TF_VAR_billing_account, and TF_VAR_seed_project_id before `terraform init`.
Open in the console / docs โ - 8
Create the Google Groups BEFORE enforcing domain restriction
Every IAM binding in this skill targets a Google Group, never a user and never a raw service account email. Create them in the Google Admin console at Directory > Groups > Create group using the agreed naming scheme (e.g. [email protected]), and get the grants in place first โ enforcing iam.allowedPolicyMemberDomains before the groups and automation identities exist locks you out and instantly cuts off external partners.
Open in the console / docs โ - 9
Dry-run the org policy baseline before enforcing anything
Every constraint is a potential outage. Simulate first: `gcloud policy-intelligence simulate orgpolicy --organization="$ORG_ID" --policies=policies/vm-external-ip.yaml`, then after apply confirm with `gcloud org-policies describe constraints/compute.vmExternalIpAccess --organization="$ORG_ID" --effective`. Baseline set at the org node: iam.disableServiceAccountKeyCreation, iam.automaticIamGrantsForDefaultServiceAccounts, iam.allowedPolicyMemberDomains, compute.skipDefaultNetworkCreation, compute.requireShieldedVm, compute.disableSerialPortAccess, storage.publicAccessPrevention, storage.uniformBucketLevelAccess, sql.restrictPublicIp, essentialcontacts.allowedContactDomains, gcp.resourceLocations. Grant exceptions with a child policy carrying inherit_from_parent = true plus a rule, never by deleting the parent policy.
Open in the console / docs โ - 10
Turn on the BigQuery billing export and Essential Contacts
Cloud Console > Billing > select account > Billing export > BIGQUERY EXPORT tab > EDIT SETTINGS on both 'Standard usage cost' and 'Pricing', pointing at a dataset in the seed/logging project. The export is not retroactive, so enable it on day one or you have no data for the 90-day CUD analysis. Then register Essential Contacts for the billing and security categories at IAM & Admin > Essential Contacts on the org node.
Open in the console / docs โ - 11
Create budgets with a forecasted-spend rule
`gcloud billing budgets create --billing-account="$BILLING_ID" --display-name=org-monthly --budget-amount=50000USD --threshold-rule=percent=0.5 --threshold-rule=percent=0.9 --threshold-rule=percent=1.0,basis=forecasted-spend`, repeated per environment folder. Budgets DO NOT cap spend โ the only hard-cap mechanism is a Pub/Sub-triggered function calling projects.updateBillingInfo with an empty billing account, which destroys running resources. Get explicit written sign-off before building that.
Open in the console / docs โ - 12
Wire the aggregated log sink's writer identity
After `google_logging_organization_sink` with include_children = true is created, grant its returned writer_identity roles/bigquery.dataEditor on the destination dataset (google_bigquery_dataset_iam_member) or roles/storage.objectCreator on the destination bucket. Miss this and the sink drops every record silently. Inspect sinks at Cloud Console > Logging > Log Router with the organization selected.
Open in the console / docs โ - 13
Baseline the estate and prove no residual owner grants
`gcloud asset export --organization="$ORG_ID" --content-type=resource --output-path="gs://${SEED}-tfstate/asset-baseline.json"` then `gcloud asset search-all-iam-policies --scope="organizations/$ORG_ID" --query='policy:roles/owner'`. Any roles/owner outside the seed project is a finding. Schedule `terraform plan` in CI for drift detection and enable Security Command Center at the org node.
Open in the console / docs โ
Check it worked
gcloud organizations describe "$ORG_ID" --format='value(displayName)' && gcloud billing accounts describe "$BILLING_ID" --format='value(open)' && gcloud resource-manager folders list --organization="$ORG_ID" --format='value(displayName)' && gcloud org-policies describe constraints/compute.vmExternalIpAccess --organization="$ORG_ID" --effective && gcloud storage ls "gs://${SEED}-tfstate" && echo OK-ORG-BILLING-POLICY-STATEIf you hit an error
PERMISSION_DENIED: Permission 'resourcemanager.organizations.setIamPolicy' denied on resource '//cloudresourcemanager.googleapis.com/organizations/123456789012' (or it may not exist).Cause: The caller holds project-level roles only; org-node bindings require roles/resourcemanager.organizationAdmin, which on a fresh org only a Cloud Identity super admin can grant to themselves first.
Fix: Have a Cloud Identity super admin open Cloud Console with the ORGANIZATION selected in the resource picker, IAM & Admin > IAM > GRANT ACCESS, and add Organization Administrator to your group. Re-check with `gcloud organizations get-iam-policy "$ORG_ID"`. Note the '(or it may not exist)' suffix is generic โ it usually means denied, not missing.
Your application is authenticating by using local Application Default Credentials. The cloudresourcemanager.googleapis.com API requires a quota project, which is not set by default.Cause: ADC from `gcloud auth application-default login` has no quota project, and the Terraform google provider was not given one either.
Fix: Run `gcloud auth application-default set-quota-project "$SEED"` and set `billing_project = var.seed_project_id` with `user_project_override = true` in the provider block. This is the single most common Terraform failure on org-level resources.
Error 403: The caller does not have permission, forbidden โ on google_org_policy_policyCause: Missing roles/orgpolicy.policyAdmin, or the policy name/parent is a folder the caller only has projectCreator on.
Fix: Grant roles/orgpolicy.policyAdmin at the org node (it does not inherit downward from a project grant) and confirm the resource `parent` string is exactly `organizations/ORG_ID` or `folders/FOLDER_ID`, not a project.
FAILED_PRECONDITION: Key creation is not allowed on this service account. โ constraints/iam.disableServiceAccountKeyCreationCause: Step 5 of the skill enforces iam.disableServiceAccountKeyCreation org-wide, so any attempt to mint a JSON service account key for CI is rejected by design.
Fix: Do not create a key. Use Workload Identity Federation with `gcloud iam workload-identity-pools create-cred-config` and point GOOGLE_APPLICATION_CREDENTIALS at that external_account file. If you truly need a temporary exception, add a child org policy on the bootstrap folder with inherit_from_parent = true and an allow rule โ never delete the parent policy.
INVALID_ARGUMENT: The attribute condition must reference one of the provider's claims.Cause: `gcloud iam workload-identity-pools providers create-oidc` was given an --attribute-mapping containing attribute.repository (or any non-subject attribute) without a matching --attribute-condition, which Google blocks because it would let any repo on the issuer assume the identity.
Fix: Add `--attribute-condition="assertion.repository=='ORG/REPO'"` (single quotes inside, matching a claim actually present in the mapping). See https://cloud.google.com/iam/help/workload-identity/attribute-condition.
The project to be billed is associated with an absent billing account.Cause: The billing account is closed or the caller lost roles/billing.user, so `gcloud billing projects link` silently left the project unlinked and the next API call fails.
Fix: Confirm `gcloud billing accounts describe "$BILLING_ID" --format='value(open)'` returns True, re-run `gcloud billing projects link "$SEED" --billing-account="$BILLING_ID"`, and verify with `gcloud billing projects describe "$SEED"`.
Error 429: Quota exceeded for quota metric 'Project creation requests' and limit 'Project creation requests per minute' of service cloudresourcemanager.googleapis.comCause: Bulk-creating dozens of workload projects in one apply hits the per-minute project creation rate limit, and separately the billing account has a hard cap on total projects.
Fix: Request a project-creation quota increase at IAM & Admin > Quotas BEFORE the build, then throttle Terraform with `terraform apply -parallelism=5` and split project creation into its own state layer so a retry does not re-plan the org.
Request had insufficient authentication scopes. (generic::permission_denied)Cause: Running gcloud or Terraform from a Compute Engine VM whose default service account was created with restricted OAuth scopes, so cloud-platform is not granted regardless of IAM roles.
Fix: Recreate the runner VM with `--scopes=https://www.googleapis.com/auth/cloud-platform`, or better, run CI through Workload Identity Federation instead of a VM's default service account.
Constraint constraints/gcp.resourceLocations violated for projects/... attempted location us โ multi-region bucket creation failsCause: gcp.resourceLocations was scoped by enumerating individual regions, which blocks multi-region GCS buckets, some Cloud Build workers, and global load balancer components.
Fix: Replace the enumerated region list with a value group such as `in:us-locations` in the allowed_values list, then re-run `gcloud org-policies describe constraints/gcp.resourceLocations --organization="$ORG_ID" --effective`.
Error 400: Precondition check failed., failedPrecondition โ on google_project or google_project_serviceCause: Usually a missing enabled API on the quota/billing project, an unlinked billing account, or the org policy compute.skipDefaultNetworkCreation interacting with auto_create_network.
Fix: Enable cloudresourcemanager.googleapis.com, cloudbilling.googleapis.com, and serviceusage.googleapis.com on the seed project, confirm billing is linked, and set `auto_create_network = false` on every google_project resource.
Error creating Sink: googleapi: Error 403: The caller does not have permission โ google_logging_organization_sinkCause: Missing roles/logging.configWriter at the org node; a project-level grant does not cover organizations.sinks.create.
Fix: Grant roles/logging.configWriter on the organization. Separately, after creation grant the sink's writer_identity roles/bigquery.dataEditor on the destination dataset or the sink will accept writes and drop every record.
Error 400: Cannot enable resource as a service project: the host project has reached the maximum number of attached service projects., badRequestCause: The default ceiling of roughly 100 service projects per Shared VPC host project was reached.
Fix: Add a second host project for that environment or region, or request a Shared VPC quota increase at IAM & Admin > Quotas well before you approach the limit โ moving a host project after service projects attach is disruptive.
Official documentation: https://cloud.google.com/architecture/landing-zones โ
API operations this skill uses (20)
Every call the skill makes, linked to its official reference page.
| Operation | Call | Permission | Ref |
|---|---|---|---|
| Search organizations (discover the org node) Step 1 of the procedure: confirm an organization node exists and capture ORG_ID plus the Cloud Identity directoryCustomerId before anything else runs. Paired with `gcloud resource-manager folders list --organization=ORG_ID` and `gcloud projects list --format='table(projectId,parent.id,parent.type)'` for brownfield discovery of the existing hierarchy. | POST https://cloudresourcemanager.googleapis.com/v3/organizations:search (CLI: gcloud organizations list) | resourcemanager.organizations.get (roles/resourcemanager.organizationViewer) | docs โ |
| Get organization IAM policy (caller role check) Enumerate the caller's org-level roles so the skill can list what is missing and stop rather than half-applying the landing zone. | POST https://cloudresourcemanager.googleapis.com/v3/organizations/{ORG_ID}:getIamPolicy (CLI: gcloud organizations get-iam-policy ORG_ID --flatten='bindings[]') | resourcemanager.organizations.getIamPolicy | docs โ |
| Create folder (prod / nonprod / common / bootstrap) Builds the environment or business-unit hierarchy that every org policy, budget, and IAM binding is anchored to. | POST https://cloudresourcemanager.googleapis.com/v3/folders body: {"displayName":"prod","parent":"organizations/{ORG_ID}"} (Terraform: google_folder) | resourcemanager.folders.create (roles/resourcemanager.folderCreator) | docs โ |
| Create project (seed, network host, workload service projects) One workload equals one project, with the env/owner/cost-center/data-class label contract applied at creation because labels are the only reliable cost-attribution axis in the billing export. | POST https://cloudresourcemanager.googleapis.com/v3/projects body: {"projectId":"acme-net-host-prod","parent":"folders/{FOLDER_ID}","labels":{...}} (CLI: gcloud projects create) | resourcemanager.projects.create (roles/resourcemanager.projectCreator) | docs โ |
| Link project to billing account Links the seed and every created project to billing after `gcloud billing accounts list` confirms the account is OPEN. This is also the exact call a hard-spend-cap function would abuse with an empty billingAccountName โ which destroys running resources and needs explicit sign-off. | PUT https://cloudbilling.googleapis.com/v1/projects/{PROJECT_ID}/billingInfo body: {"billingAccountName":"billingAccounts/{BILLING_ID}"} (CLI: gcloud billing projects link) | billing.resourceAssociations.create + resourcemanager.projects.createBillingAssignment (roles/billing.user + roles/resourcemanager.projectBillingManager) | docs โ |
| Batch-enable APIs on a project Turns on resourcemanager, billing, iam, orgpolicy, compute, logging, storage, cloudasset, and billingbudgets on the seed project so every later call in the skill has an enabled API and a quota project. Paired with storage.buckets.insert (`gcloud storage buckets create --uniform-bucket-level-access --public-access-prevention`) for the hand-created, versioned Terraform state bucket, since Terraform cannot create its own backend. | POST https://serviceusage.googleapis.com/v1/projects/{PROJECT_NUMBER}/services:batchEnable (CLI: gcloud services enable cloudresourcemanager.googleapis.com orgpolicy.googleapis.com cloudasset.googleapis.com ...) | serviceusage.services.enable (roles/serviceusage.serviceUsageAdmin) | docs โ |
| Create the CI workload identity pool and GitHub OIDC provider Federates CI to token.actions.githubusercontent.com with a mandatory attributeCondition pinning assertion.repository, so the pipeline never needs a service account key (org policy iam.disableServiceAccountKeyCreation blocks those) and no other repo can assume the runner identity. | POST https://iam.googleapis.com/v1/projects/{SEED}/locations/global/workloadIdentityPools?workloadIdentityPoolId=ci then .../workloadIdentityPools/ci/providers?workloadIdentityPoolProviderId=github with oidc.issuerUri, attributeMapping, attributeCondition (CLI: gcloud iam workload-identity-pools create ci --location=global; gcloud iam workload-identity-pools providers create-oidc github ...) | iam.workloadIdentityPools.create + iam.workloadIdentityPoolProviders.create (roles/iam.workloadIdentityPoolAdmin) | docs โ |
| Set an organization policy (constraint baseline) Applies the org-node baseline: disableServiceAccountKeyCreation, automaticIamGrantsForDefaultServiceAccounts, allowedPolicyMemberDomains, skipDefaultNetworkCreation, requireShieldedVm, disableSerialPortAccess, publicAccessPrevention, uniformBucketLevelAccess, restrictPublicIp, allowedContactDomains, resourceLocations. | POST https://orgpolicy.googleapis.com/v2/organizations/{ORG_ID}/policies body: {"name":"organizations/{ORG_ID}/policies/iam.disableServiceAccountKeyCreation","spec":{"rules":[{"enforce":true}]}} (Terraform: google_org_policy_policy) | orgpolicy.policy.set (roles/orgpolicy.policyAdmin) | docs โ |
| Simulate an org policy change before enforcing Every constraint is a potential outage, so the skill dry-runs each policy against the live estate and reports would-be violations before any enforcing apply. | CLI gcloud policy-intelligence simulate orgpolicy --organization="$ORG_ID" --policies=policies/vm-external-ip.yaml | policysimulator.orgPolicyViolationsPreviews.create + cloudasset.assets.searchAllResources (roles/policysimulator.admin + roles/cloudasset.viewer) | docs โ |
| Read back the effective org policy Confirms inheritance actually resolved as intended after child policies with inherit_from_parent = true add exceptions โ deleting a parent policy is never the right fix. | GET https://orgpolicy.googleapis.com/v2/organizations/{ORG_ID}/policies/compute.vmExternalIpAccess:getEffectivePolicy (CLI: gcloud org-policies describe constraints/compute.vmExternalIpAccess --organization=ORG_ID --effective) | orgpolicy.policy.get (roles/orgpolicy.policyViewer) | docs โ |
| Enable a project as a Shared VPC host Creates one Shared VPC host project per environment so a prod VM can never land on a nonprod subnet, and collapses Cloud NAT gateways from per-project to per-environment-per-region. | POST https://compute.googleapis.com/compute/v1/projects/{HOST}/enableXpnHost (Terraform: google_compute_shared_vpc_host_project) | compute.organizations.enableXpnHost (roles/compute.xpnAdmin at org or folder scope) | docs โ |
| Attach a service project to the Shared VPC host Attaches each workload project to its environment host. The default ceiling is roughly 100 service projects per host, so the skill plans a second host or a quota increase before approaching it. | POST https://compute.googleapis.com/compute/v1/projects/{HOST}/enableXpnResource body: {"xpnResource":{"id":"SERVICE_PROJECT_ID","type":"PROJECT"}} (Terraform: google_compute_shared_vpc_service_project) | compute.organizations.enableXpnResource (roles/compute.xpnAdmin) | docs โ |
| Create a subnet with sampled flow logs and secondary ranges Allocates a /16 per region per environment, keeps GKE pod ranges out of RFC1918 space, and cuts VPC Flow Log ingestion roughly 90% โ the single largest logging cost lever. | POST https://compute.googleapis.com/compute/v1/projects/{HOST}/regions/{REGION}/subnetworks body includes ipCidrRange, secondaryIpRanges (GKE pods from 100.64.0.0/10), privateIpGoogleAccess, logConfig.flowSampling=0.1, logConfig.metadata=EXCLUDE_ALL_METADATA (Terraform: google_compute_subnetwork) | compute.subnetworks.create (roles/compute.networkAdmin on the host project) | docs โ |
| Grant networkUser at SUBNET scope Gives each team exactly one subnet instead of the whole host project โ the checklist item that most often fails a review. Use additive google_compute_subnetwork_iam_member, never the authoritative _iam_policy resource. | POST https://compute.googleapis.com/compute/v1/projects/{HOST}/regions/{REGION}/subnetworks/{SUBNET}/setIamPolicy (CLI: gcloud compute networks subnets add-iam-policy-binding sn-app-us-central1-prod --region=us-central1 --project="$HOST" --member="group:[email protected]" --role=roles/compute.networkUser) | compute.subnetworks.setIamPolicy (roles/compute.networkAdmin or roles/compute.xpnAdmin) | docs โ |
| Create the aggregated organization log sink Routes every child project's audit logs into one logging project with includeChildren=true, excluding healthy load balancer requests that dominate volume and prove nothing. The returned writerIdentity must then be granted write access on the destination or the sink drops everything. | POST https://logging.googleapis.com/v2/organizations/{ORG_ID}/sinks?uniqueWriterIdentity=true body: {"name":"org-audit-to-bq","destination":"bigquery.googleapis.com/projects/{SEED}/datasets/org_audit_logs","includeChildren":true,"filter":"logName:\"logs/cloudaudit.googleapis.com\"","exclusions":[...]} (Terraform: google_logging_organization_sink) | logging.sinks.create on the organization (roles/logging.configWriter) | docs โ |
| Create an IAM deny policy at the org node Backstops the service-account-key org policy: deny rules are evaluated before allow rules, so no project-level grant anywhere in the hierarchy can override them. | POST https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Forganizations%2F{ORG_ID}/denypolicies?policyId=no-sa-keys (CLI: gcloud iam policies create no-sa-keys --kind=denypolicies --attachment-point="cloudresourcemanager.googleapis.com/organizations/$ORG_ID" --policy-file=deny.json) | iam.denypolicies.create (roles/iam.denyAdmin) | docs โ |
| Create billing budgets with a forecasted-spend threshold Adds org-level and per-environment-folder budgets at 50/90/100% including a forecast rule. The skill states explicitly that budgets alert but do not cap spend. | POST https://billingbudgets.googleapis.com/v1/billingAccounts/{BILLING_ID}/budgets body includes budgetFilter (folder/project scope), amount.specifiedAmount, thresholdRules with basis=FORECASTED_SPEND (CLI: gcloud billing budgets create --threshold-rule=percent=1.0,basis=forecasted-spend) | billing.budgets.create (roles/billing.admin or roles/billing.costsManager) | docs โ |
| Export the Cloud Asset Inventory baseline Captures a point-in-time snapshot of the whole estate for the hand-off package and for later drift comparison. | POST https://cloudasset.googleapis.com/v1/organizations/{ORG_ID}:exportAssets body: {"contentType":"RESOURCE","outputConfig":{"gcsDestination":{"uri":"gs://{SEED}-tfstate/asset-baseline.json"}}} (CLI: gcloud asset export --organization=ORG_ID --content-type=resource) | cloudasset.assets.exportResource (roles/cloudasset.viewer at org scope) | docs โ |
| Search all IAM policies for residual owner grants Validation step: any roles/owner binding outside the seed project is a finding in the risk register, along with any binding targeting a user instead of a Google Group. | GET https://cloudasset.googleapis.com/v1/organizations/{ORG_ID}:searchAllIamPolicies?query=policy:roles/owner (CLI: gcloud asset search-all-iam-policies --scope="organizations/$ORG_ID" --query='policy:roles/owner') | cloudasset.assets.searchAllIamPolicies (roles/cloudasset.viewer) | docs โ |
| List IAM policy recommendations for right-sizing Day-2 least-privilege pass: after 60-90 days of real traffic, harvest recommendations to shrink over-broad role bindings toward the permissions actually exercised. | GET https://recommender.googleapis.com/v1/projects/{PROJECT_ID}/locations/global/recommenders/google.iam.policy.Recommender/recommendations (CLI: gcloud recommender recommendations list --recommender=google.iam.policy.Recommender --location=global) | recommender.iamPolicyRecommendations.list (roles/recommender.iamViewer) | docs โ |