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
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:
- Identify which identity Terraform is using
- Read the missing permission from the error
- Find the smallest predefined role that includes that permission
- 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.
- Read the error. Find the exact missing permission (e.g.,
storage.buckets.create) and the identity that was denied. - Confirm the active identity. Run
gcloud auth listand check that Terraform is using the identity you expect. If using ADC, rungcloud auth application-default print-access-tokenand decode it. - Classify the problem. Is it a missing permission, wrong identity, disabled API, or org policy restriction? Each has a different fix.
- 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.
- Re-run plan or apply. If another permission is missing, repeat from step 1.
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 error | What it tells you | Example value |
|---|---|---|
| Active identity | The user or service account Terraform authenticated as | xxx@developer.gserviceaccount.com |
| Missing permission | The exact permission GCP expected the identity to have | storage.buckets.create |
| Target resource | The resource or scope the permission applies to | projects/my-project |
| Action | What Terraform was trying to do when it was denied | Creating a storage bucket |
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.
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:
- The
credentialsfield in the provider block (inline JSON or file path) - The
GOOGLE_CREDENTIALSenvironment variable (JSON string) - The
GOOGLE_APPLICATION_CREDENTIALSenvironment variable (path to a key file) - 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
GOOGLE_APPLICATION_CREDENTIALSenvironment variable pointing to a credentials file- Local ADC file created by
gcloud auth application-default login(stored at~/.config/gcloud/application_default_credentials.json) - Attached service account from the metadata server (GCE, Cloud Run, GKE, Cloud Build)
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.comFor 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.
| Symptom | What it usually means | How to verify | Typical fix |
|---|---|---|---|
| 403 with a specific missing permission | The identity lacks the required IAM permission | Check the identity and permission in the error message | Grant the predefined role containing that permission |
| 403 but the identity shown is unexpected | Terraform is using the wrong credentials | Run gcloud auth list and check GOOGLE_CREDENTIALS / GOOGLE_APPLICATION_CREDENTIALS | Fix 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 project | Run gcloud services list —enabled —project=PROJECT_ID | Enable the API or add a google_project_service resource. See API Not Enabled Errors |
| 403 referencing an org policy constraint | An organization policy is blocking the action regardless of IAM roles | Run gcloud org-policies list —project=PROJECT_ID | Work within the policy constraint or request an exception from your org admin |
403 on terraform init accessing the GCS state bucket | The identity lacks access to the state backend bucket | Run gcloud storage ls gs://STATE_BUCKET/ as the Terraform identity | Grant roles/storage.objectAdmin on the state bucket |
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 task | Example missing permission | Typical minimal role | Scope | Notes |
|---|---|---|---|---|
| Create a Cloud Storage bucket | storage.buckets.create | roles/storage.admin | Project | |
| Enable a GCP API | serviceusage.services.enable | roles/serviceusage.serviceUsageAdmin | Project | Or enable APIs manually before the first apply |
| Manage project-level IAM bindings | resourcemanager.projects.setIamPolicy | roles/iam.securityAdmin | Project | Separate from resource management roles |
| Manage service account IAM | iam.serviceAccounts.setIamPolicy | roles/iam.serviceAccountAdmin | Project or service account | Required for google_service_account_iam_* resources |
| Write Terraform state to a GCS bucket | storage.objects.create | roles/storage.objectAdmin | State bucket | Grant on the bucket, not the project |
| Create Compute Engine instances | compute.instances.create | roles/compute.admin | Project | |
| Create GKE clusters | container.clusters.create | roles/container.admin | Project | Also needs roles/iam.serviceAccountUser for node SA |
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)
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 inroles/iam.securityAdmin - Service account IAM:
iam.serviceAccounts.setIamPolicy, included inroles/iam.serviceAccountAdmin - Bucket IAM:
storage.buckets.setIamPolicy, included inroles/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.
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).
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
planorapply - The same Terraform config works locally but fails in CI/CD
terraform initfails 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
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.
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.securityAdminor the equivalent setIamPolicy permission on the target resource.Not enabling required APIs before running Terraform. Terraform cannot call APIs that are disabled on the project. Either add
google_project_serviceresources or enable APIs manually before the first apply. See API Not Enabled Errors for details.Granting broad roles instead of mapping the missing permission. Granting
roles/editororroles/ownerto 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.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.
Summary
- Read the exact missing permission and identity from the 403 error before granting anything
- Confirm which identity Terraform is using. Wrong credentials are as common as missing roles
- Classify the problem: missing permission, wrong identity, disabled API, or org policy
- Grant the narrowest predefined role at the correct scope instead of broad roles like
roles/editor - Managing IAM resources requires
setIamPolicypermissions, separate from resource admin roles - The GCS state bucket needs
roles/storage.objectAdminfor the Terraform identity - Use Workload Identity Federation in CI/CD instead of service account keys
- Test locally with service account impersonation to catch permission gaps before they reach the pipeline
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.