Terraform 403 Permission Errors in GCP: Find Missing Roles Fast

When Terraform returns a 403 error during plan or apply, GCP is telling you that the authenticated identity is missing a specific permission. The error message contains the exact permission name, the identity that was denied, and the target resource. You do not need to guess what went wrong.

The fastest path to a fix: read the missing permission from the error, confirm which identity Terraform is actually using, map the permission to the narrowest predefined role, and grant that role at the correct scope. This page walks through each step and covers the most common scenarios that cause Terraform permission failures in GCP.

What a Terraform 403 actually means

Analogy: the building security desk

Think of GCP like a building with a security desk. Terraform walks in with a badge (your credentials) and asks to enter a specific room (create a resource). The guard checks the badge, looks at the access list, and either lets Terraform through or says “you are not on the list for that room.” A 403 error is that guard saying no and telling you exactly which room was denied.

Terraform authenticates to GCP as a specific identity, either your personal Google account or a service account. Every GCP API call requires the caller to hold certain IAM permissions. When the authenticated identity is missing a required permission, GCP rejects the request with HTTP 403 and returns the exact permission it expected.

The fix always follows the same pattern:

  1. Identify which identity Terraform is using
  2. Read the missing permission from the error
  3. Find the smallest predefined role that includes that permission
  4. Grant the role at the correct scope (project, folder, or resource)

This is the same IAM model that applies to any GCP access denial. If you want a broader overview of how GCP permission errors work beyond Terraform, see Permission Denied Errors.

Fast fix checklist

Follow these steps when Terraform returns a 403. Most issues resolve in under a minute once you read the error carefully.

  1. Read the error. Find the exact missing permission (e.g., storage.buckets.create) and the identity that was denied.
  2. Confirm the active identity. Run gcloud auth list and check that Terraform is using the identity you expect. If using ADC, run gcloud auth application-default print-access-token and decode it.
  3. Classify the problem. Is it a missing permission, wrong identity, disabled API, or org policy restriction? Each has a different fix.
  4. Grant the minimal role. Look up the permission in the IAM permissions reference to find the narrowest predefined role. Grant it at the correct scope using gcloud IAM commands.
  5. Re-run plan or apply. If another permission is missing, repeat from step 1.
Why this works

GCP 403 errors are unusually helpful compared to other cloud providers. They include the exact permission name, making it possible to find and fix the gap without trial and error. Trust the error message.

How to read a Terraform 403 error

A typical Terraform 403 error contains four pieces of information:

Error: Error creating Bucket: googleapi: Error 403:
xxx@developer.gserviceaccount.com does not have storage.buckets.create
access to the Google Cloud project.
Required 'storage.buckets.create' permission for 'projects/my-project'.
Part of the errorWhat it tells youExample value
Active identityThe user or service account Terraform authenticated asxxx@developer.gserviceaccount.com
Missing permissionThe exact permission GCP expected the identity to havestorage.buckets.create
Target resourceThe resource or scope the permission applies toprojects/my-project
ActionWhat Terraform was trying to do when it was deniedCreating a storage bucket
Check the identity first

If the identity shown in the error is not the one you expected, the problem is authentication, not authorization. No amount of role granting will fix a wrong-identity issue. Jump to the credentials section below.

How Terraform authenticates with GCP

The Terraform Google provider supports several credential methods. These are not the same as Google Application Default Credentials (ADC), though some of them overlap.

Analogy: a stack of ID cards

Terraform checks a stack of possible ID cards in a fixed order. It uses the first valid one it finds and ignores the rest. If you set GOOGLE_CREDENTIALS to a service account key, Terraform will never reach your local ADC login, even if that login has the right permissions. Understanding the order tells you which card Terraform is actually showing to GCP.

Terraform provider credential lookup

The google provider checks for credentials in this order:

  1. The credentials field in the provider block (inline JSON or file path)
  2. The GOOGLE_CREDENTIALS environment variable (JSON string)
  3. The GOOGLE_APPLICATION_CREDENTIALS environment variable (path to a key file)
  4. Application Default Credentials (ADC)

When the provider falls through to ADC, the standard Google ADC lookup order applies:

Google Application Default Credentials (ADC) lookup order

  1. GOOGLE_APPLICATION_CREDENTIALS environment variable pointing to a credentials file
  2. Local ADC file created by gcloud auth application-default login (stored at ~/.config/gcloud/application_default_credentials.json)
  3. Attached service account from the metadata server (GCE, Cloud Run, GKE, Cloud Build)
Provider credentials vs ADC

The provider-level GOOGLE_CREDENTIALS variable and the provider credentials field are Terraform-specific. They are checked before ADC and are not part of the standard ADC chain. This is a common source of confusion when debugging identity issues.

Checking and setting credentials

# Check which ADC credentials are currently active
gcloud auth application-default print-access-token

# Set up ADC for local development with your personal account
gcloud auth application-default login

# Test as a service account without downloading a key file
gcloud auth application-default login \
  --impersonate-service-account=terraform@my-project.iam.gserviceaccount.com

For local development, service account impersonation lets you test with the same identity that CI/CD uses, without downloading key files. This catches permission gaps before they reach the pipeline.

For CI/CD pipelines, use Workload Identity Federation to authenticate without long-lived service account keys. GitHub Actions, GitLab CI, and most major CI providers support it. Only fall back to service account keys if your CI system has no federation support and no other option exists.

Permission denied vs wrong identity vs API not enabled vs policy deny

Not every Terraform failure is a missing role. Use this table to classify the problem before granting anything.

SymptomWhat it usually meansHow to verifyTypical fix
403 with a specific missing permissionThe identity lacks the required IAM permissionCheck the identity and permission in the error messageGrant the predefined role containing that permission
403 but the identity shown is unexpectedTerraform is using the wrong credentialsRun gcloud auth list and check GOOGLE_CREDENTIALS / GOOGLE_APPLICATION_CREDENTIALSFix the credential configuration so the correct identity is active
403 with SERVICE_DISABLED or “API not enabled”The required API is not enabled on the projectRun gcloud services list —enabled —project=PROJECT_IDEnable the API or add a google_project_service resource. See API Not Enabled Errors
403 referencing an org policy constraintAn organization policy is blocking the action regardless of IAM rolesRun gcloud org-policies list —project=PROJECT_IDWork within the policy constraint or request an exception from your org admin
403 on terraform init accessing the GCS state bucketThe identity lacks access to the state backend bucketRun gcloud storage ls gs://STATE_BUCKET/ as the Terraform identityGrant roles/storage.objectAdmin on the state bucket
Do not skip classification

Granting more roles will not fix a wrong-identity problem, a disabled API, or an org policy restriction. Spending 30 seconds classifying the error saves you from chasing the wrong fix.

Common Terraform operations and the roles they need

The right role depends on what Terraform manages. Do not grant broad roles preemptively. Start with nothing extra, run terraform plan, and add roles as 403 errors identify missing permissions. This follows the principle of least privilege.

Terraform taskExample missing permissionTypical minimal roleScopeNotes
Create a Cloud Storage bucketstorage.buckets.createroles/storage.adminProject
Enable a GCP APIserviceusage.services.enableroles/serviceusage.serviceUsageAdminProjectOr enable APIs manually before the first apply
Manage project-level IAM bindingsresourcemanager.projects.setIamPolicyroles/iam.securityAdminProjectSeparate from resource management roles
Manage service account IAMiam.serviceAccounts.setIamPolicyroles/iam.serviceAccountAdminProject or service accountRequired for google_service_account_iam_* resources
Write Terraform state to a GCS bucketstorage.objects.createroles/storage.objectAdminState bucketGrant on the bucket, not the project
Create Compute Engine instancescompute.instances.createroles/compute.adminProject
Create GKE clusterscontainer.clusters.createroles/container.adminProjectAlso needs roles/iam.serviceAccountUser for node SA
Finding the right role

When a permission is missing, look it up in the IAM permissions reference to find the predefined roles that include it. Pick the narrowest one. For a deeper understanding of how roles, permissions, and bindings work, see IAM Roles Explained.

IAM resource permissions (the most common surprise)

Analogy: building manager vs key maker

Having roles/storage.admin is like being the building manager who can create and destroy rooms. But handing out keys to other people requires a separate authority. In GCP, creating a resource and granting others access to that resource are two different permissions. The “key maker” permission is setIamPolicy, and it lives in a different role.

When Terraform manages IAM resources like google_project_iam_binding or google_storage_bucket_iam_member, it needs permission to modify IAM policies. This is separate from the permission to manage the resource itself. A service account with roles/storage.admin can create buckets, but it cannot grant other identities access to those buckets unless it also has storage.buckets.setIamPolicy.

For a complete guide to managing IAM through Terraform, including the differences between member, binding, and policy resources, see Managing IAM with Terraform.

# This resource requires roles/iam.securityAdmin on the project
# because it modifies the project's IAM policy
resource "google_project_iam_binding" "viewer" {
  project = var.project_id
  role    = "roles/viewer"
  members = ["serviceAccount:${google_service_account.app.email}"]
}
  • Project IAM: resourcemanager.projects.setIamPolicy, included in roles/iam.securityAdmin
  • Service account IAM: iam.serviceAccounts.setIamPolicy, included in roles/iam.serviceAccountAdmin
  • Bucket IAM: storage.buckets.setIamPolicy, included in roles/storage.admin

GCS backend and Terraform state bucket permission errors

If Terraform uses a GCS bucket for remote state, the authenticated identity needs object-level access to that specific bucket. This is separate from any permissions needed for the resources Terraform manages.

Analogy: the shared notebook

The state bucket is like a shared notebook where Terraform writes down what it built. Even if Terraform has permission to build everything in the project, it still needs separate permission to open, write in, and lock that notebook. Without it, Terraform knows what to build but cannot record its work.

# backend.tf
terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "terraform/state"
  }
}
# Grant the Terraform service account access to the state bucket
gcloud storage buckets add-iam-policy-binding gs://my-terraform-state \
  --member="serviceAccount:terraform@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

roles/storage.objectAdmin on the state bucket allows Terraform to read, write, and lock the state file. A narrower role like roles/storage.objectViewer allows terraform plan (which only reads state) but blocks terraform apply (which writes state).

Plan works but apply fails?

If terraform plan succeeds but terraform apply fails with a GCS permission error, the identity likely has read access but not write access to the state bucket. Grant roles/storage.objectAdmin on the bucket to fix it.

For more on remote state configuration, locking, and backend setup, see Terraform State Management.

When to use this guide

This page is the right starting point when:

  • Terraform returns a 403 error during plan or apply
  • The same Terraform config works locally but fails in CI/CD
  • terraform init fails with a GCS backend access error
  • Terraform can manage resources but fails when creating IAM bindings
  • You are setting up a new Terraform service account and need to know which roles to start with

If Terraform is failing for reasons other than permission errors, the Terraform for Google Cloud guide covers setup, authentication, and general workflow.

Common mistakes

  1. Testing locally with personal credentials, then debugging CI/CD failures. Your personal account often has Owner or Editor access. The CI/CD service account only has the roles you granted. Use service account impersonation locally to match the pipeline identity.

  2. Forgetting IAM-specific permissions when Terraform manages IAM. Resource admin roles do not include permission to modify IAM policies. If Terraform creates IAM bindings, the service account needs roles/iam.securityAdmin or the equivalent setIamPolicy permission on the target resource.

  3. Not enabling required APIs before running Terraform. Terraform cannot call APIs that are disabled on the project. Either add google_project_service resources or enable APIs manually before the first apply. See API Not Enabled Errors for details.

  4. Granting broad roles instead of mapping the missing permission. Granting roles/editor or roles/owner to silence a 403 works in the short term but violates least privilege and creates security risk. Read the missing permission, find the narrowest predefined role, and grant that.

  5. Using service account keys in CI/CD pipelines. Key files are long-lived credentials that are a security risk if leaked. Use Workload Identity Federation instead. Most major CI providers support it.

Frequently asked questions

What role does Terraform need in GCP?

There is no single role. The roles Terraform needs depend entirely on what resources it manages. Each resource type requires specific permissions. Read the exact missing permission from the 403 error, find the predefined role that includes it using the IAM permissions reference, and grant that role at the correct scope. Start with zero extra roles and add as errors identify what is missing.

Why does Terraform work locally but fail in CI/CD?

Your personal Google account likely has broad access such as Owner or Editor. The CI/CD service account only has the roles you explicitly granted. Test locally with the same service account using gcloud auth application-default login --impersonate-service-account=SA_EMAIL to reproduce CI/CD failures before pushing changes.

Why can Terraform read resources but not create them?

Read operations use GET and LIST permissions. Create operations require separate CREATE permissions. A Viewer role grants read-only access. Check the 403 error for the exact missing permission, then find the predefined role that includes it. For example, storage.buckets.get is in roles/storage.objectViewer but storage.buckets.create requires roles/storage.admin.

What role is needed to enable Google APIs?

Enabling APIs requires roles/serviceusage.serviceUsageAdmin on the project. This is separate from the roles needed to manage the resources of that API. You can also enable APIs manually before the first Terraform run and use google_project_service with disable_on_destroy = false to track them in state.

Why does Terraform fail on IAM resources even when it has service admin roles?

Managing IAM bindings requires permission to modify IAM policies, not just manage the resource itself. For project-level IAM, you need resourcemanager.projects.setIamPolicy (included in roles/iam.securityAdmin). For service account IAM, you need iam.serviceAccounts.setIamPolicy (included in roles/iam.serviceAccountAdmin). Resource admin roles do not include these IAM policy permissions.

Last verified: 27 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.