GCP Error Guide: 'PERMISSION_DENIED (403)' Caller Does Not Have Permission
Fix the GCP PERMISSION_DENIED (403) caller does not have permission error: diagnose missing IAM roles, wrong active account, disabled APIs, and org policy denials.
- #gcp
- #troubleshooting
- #errors
- #iam
Overview
A PERMISSION_DENIED error is returned when the identity making the request (your user account, a service account, or a workload identity) is not granted an IAM role that includes the specific permission the API call needs. Google Cloud evaluates the caller’s effective permissions on the target resource, finds none that allow the operation, and rejects the request with HTTP 403.
You will see this from gcloud, the client libraries, or the REST API:
ERROR: (gcloud.compute.instances.create) Could not fetch resource:
- Required 'compute.instances.create' permission for 'projects/my-prod-project/zones/us-central1-a/instances/web-01'
Or in the raw API/library form:
google.api_core.exceptions.PermissionDenied: 403 Permission 'storage.buckets.list' denied on resource (or it may not exist).
It occurs on any call where the caller lacks the permission: creating resources, listing buckets, reading secrets, deploying services. The check is per-resource and per-permission, so a caller can succeed on one project or bucket and fail on another that looks identical.
Symptoms
gcloudcommands fail withRequired '<service>.<resource>.<verb>' permission.- Client libraries raise
PermissionDenied: 403 Permission '...' denied on resource. - The operation works for you but fails in CI/CD running as a service account (or vice-versa).
- A newly created project or resource returns 403 for actions that work elsewhere.
gcloud config get-value account
gcloud compute instances list --project my-prod-project
ci-deployer@my-prod-project.iam.gserviceaccount.com
ERROR: (gcloud.compute.instances.list) Some requests did not succeed:
- Required 'compute.instances.list' permission for 'projects/my-prod-project'
Common Root Causes
1. The active account is not the one you think
gcloud may be using a different account or an impersonation/service-account credential than expected, so you are checking the wrong identity’s permissions.
gcloud auth list
gcloud config list account
Credentialed Accounts
ACTIVE ACCOUNT
* ci-deployer@my-prod-project.iam.gserviceaccount.com
james@example.com
account = ci-deployer@my-prod-project.iam.gserviceaccount.com
The active identity is the service account, not your user. Permissions must be granted to whichever identity has the *.
2. The required role is simply not bound
The caller has no role on the resource granting the permission. Check what is actually bound.
gcloud projects get-iam-policy my-prod-project \
--flatten="bindings[].members" \
--filter="bindings.members:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--format="table(bindings.role)"
ROLE
roles/logging.logWriter
roles/monitoring.metricWriter
The account can write logs and metrics but has no compute role, so compute.instances.create is denied.
3. Permission granted at the wrong scope
IAM is hierarchical (organization → folder → project → resource). A role bound on a different project, or a bucket-level grant when the call needs a project-level permission, will not apply.
gcloud projects get-iam-policy other-project \
--flatten="bindings[].members" \
--filter="bindings.members:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--format="table(bindings.role)"
ROLE
roles/compute.admin
The compute role exists, but on other-project, not on my-prod-project where the instance is being created.
4. The API is not enabled on the project
If the service API is disabled, calls return PERMISSION_DENIED (sometimes phrased as “API has not been used in project … before or it is disabled”).
gcloud services list --enabled --project my-prod-project \
--filter="config.name:compute.googleapis.com"
Listed 0 items.
No rows means the Compute Engine API is off; every compute call 403s regardless of IAM.
5. An IAM Condition excludes the request
A conditional role binding (time-based, resource-name, or tag condition) may not match the current request, so the role does not apply.
gcloud projects get-iam-policy my-prod-project --format=json \
| grep -A4 '"condition"'
"condition": {
"title": "only-dev-instances",
"expression": "resource.name.startsWith(\"projects/_/zones/us-central1-a/instances/dev-\")"
}
The condition only grants access to instances named dev-*; creating web-01 is outside the condition and denied.
6. An organization policy or VPC-SC perimeter blocks it
Org policies (e.g. iam.allowedPolicyMemberDomains) or a VPC Service Controls perimeter can deny the call even when IAM is correct. VPC-SC violations usually carry a unique ID.
gcloud logging read \
'protoPayload.status.code=7 AND protoPayload.serviceName="compute.googleapis.com"' \
--project my-prod-project --limit 1 --format=json | grep -i violation
"violationReason": "VPC_SERVICE_CONTROLS"
A VPC_SERVICE_CONTROLS violation means the perimeter, not IAM, is rejecting the request.
Diagnostic Workflow
Step 1: Confirm which identity is calling
gcloud auth list
gcloud config get-value account
If running in CI or on a VM, the credential is likely a service account or attached metadata identity, not your user.
Step 2: Read the error for the exact permission and resource
The message names both, e.g. Required 'compute.instances.create' permission for 'projects/my-prod-project/...'. Note the permission string and the full resource path — every later step keys off these.
Step 3: Test the identity’s effective permissions directly
gcloud projects test-iam-permissions my-prod-project \
--permissions=compute.instances.create
permissions: []
An empty permissions list means the active identity genuinely lacks it. (Run as the failing identity, impersonating if needed with --impersonate-service-account.)
Step 4: Verify the API is enabled
gcloud services list --enabled --project my-prod-project \
--filter="config.name:compute.googleapis.com"
If absent, enable it before chasing IAM:
gcloud services enable compute.googleapis.com --project my-prod-project
Step 5: Inspect bindings (including conditions) and grant the role
gcloud projects get-iam-policy my-prod-project \
--flatten="bindings[].members" \
--filter="bindings.members:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--format="table(bindings.role, bindings.condition.title)"
Grant the least-privilege role that covers the permission:
gcloud projects add-iam-policy-binding my-prod-project \
--member="serviceAccount:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--role="roles/compute.instanceAdmin.v1"
Allow a minute for propagation, then retry.
Example Root Cause Analysis
A CI pipeline that deploys to my-prod-project starts failing with:
ERROR: (gcloud.compute.instances.create) Could not fetch resource:
- Required 'compute.instances.create' permission for 'projects/my-prod-project/zones/us-central1-a/instances/web-01'
The job ran fine last week. gcloud auth list confirms it runs as ci-deployer@my-prod-project.iam.gserviceaccount.com. The API is enabled, so this is IAM. Listing the account’s roles:
gcloud projects get-iam-policy my-prod-project \
--flatten="bindings[].members" \
--filter="bindings.members:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--format="table(bindings.role, bindings.condition.title)"
ROLE TITLE
roles/compute.instanceAdmin.v1 only-dev-instances
The role is present but carries a condition only-dev-instances restricting it to instances named dev-*. A recent IAM cleanup added the condition; the pipeline creates web-01, which falls outside it.
Fix: re-bind the role without the over-narrow condition (or widen it):
gcloud projects add-iam-policy-binding my-prod-project \
--member="serviceAccount:ci-deployer@my-prod-project.iam.gserviceaccount.com" \
--role="roles/compute.instanceAdmin.v1"
The next pipeline run creates the instance successfully.
Prevention Best Practices
- Grant predefined least-privilege roles to service accounts at the right scope; avoid sprinkling
roles/ownerto “make it work.” - Use
gcloud projects test-iam-permissions(or the Policy Troubleshooter) in pre-deploy checks so missing permissions surface before production calls 403. - Document and review every IAM Condition — narrow
resource.nameconditions are a frequent cause of “it worked yesterday” 403s. - Enable required APIs as part of project bootstrap (Terraform
google_project_service) so disabled-API 403s never reach runtime. - Keep CI/CD identities distinct per environment so a permission gap in prod can’t be masked by your personal admin rights locally.
- For fast triage of a 403 in logs, the free incident assistant can map the denied permission to the likely missing role. More GCP walkthroughs live in the GCP guides.
Quick Command Reference
# Who am I?
gcloud auth list
gcloud config get-value account
# What does the message say I need? (read the error: permission + resource)
# Does the active identity have the permission?
gcloud projects test-iam-permissions <PROJECT> --permissions=<service.resource.verb>
# Is the API even on?
gcloud services list --enabled --project <PROJECT> --filter="config.name:<api>.googleapis.com"
gcloud services enable <api>.googleapis.com --project <PROJECT>
# What roles (and conditions) are bound to the identity?
gcloud projects get-iam-policy <PROJECT> \
--flatten="bindings[].members" \
--filter="bindings.members:<MEMBER>" \
--format="table(bindings.role, bindings.condition.title)"
# Grant a least-privilege role
gcloud projects add-iam-policy-binding <PROJECT> \
--member="serviceAccount:<SA_EMAIL>" --role="roles/<role>"
# Check for VPC-SC / org policy denials
gcloud logging read 'protoPayload.status.code=7' --project <PROJECT> --limit 1
Conclusion
A PERMISSION_DENIED (403) means the calling identity has no IAM role granting the exact permission on the exact resource named in the error. The usual root causes:
- The active
gcloudaccount is not the identity you intended to grant. - The required role is not bound to that identity at all.
- The role is bound at the wrong scope (different project or resource).
- The service API is disabled on the project.
- An IAM Condition on the binding excludes the request.
- An org policy or VPC Service Controls perimeter denies it independently of IAM.
Start from the exact permission and resource in the error, confirm the calling identity, then verify the API and the bound roles — the fix is almost always enabling an API or binding one least-privilege role at the correct scope.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.