GCS Access Denied: The IAM Role Fix That Actually Works
Google Cloud Storage returns 403 when your IAM role is missing or wrong. The fix is granting the exact role on the bucket level. Skip the big-picture stuff, just do this.
You're Getting a 403, Here's the Fix
That "AccessDenied" 403 error on Google Cloud Storage (GCS) is frustrating. You've double-checked the bucket name, the credentials are loaded, but it still fails. Drop the broad permissions—fix is strictly about the IAM role on the bucket itself.
The Fast Fix: Grant roles/storage.objectAdmin
Open Cloud Shell or your local terminal with gcloud configured. Run this single command:
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member='serviceAccount:YOUR_SERVICE_ACCT@YOUR_PROJECT.iam.gserviceaccount.com' \
--role='roles/storage.objectAdmin'
Replace YOUR_PROJECT_ID and the service account email. If you're a user (not a service account), the member field changes to user:your.email@domain.com.
Wait 60-90 seconds for propagation. Then try your read/write operation again. It should work now.
Why the Object Admin Role?
Here's what's happening. The error usually shows up when you have a role like roles/storage.admin at the project level, but the bucket has a different IAM policy that blocks it. Or you're using a role like roles/storage.objectViewer which only lets you list and get objects—not upload or delete.
roles/storage.objectAdmin gives you full control over objects inside the bucket: create, read, update, delete. It's the right balance—lets you do everything with the data but not mess with the bucket configuration (like lifecycle rules).
If you tried roles/storage.admin and it still failed, the issue is probably the bucket's IAM policy overriding the project-level role. The trick is to set the role directly on the bucket, not just the project. Use this:
gsutil iam ch serviceAccount:YOUR_SERVICE_ACCT@YOUR_PROJECT.iam.gserviceaccount.com:objectAdmin \
gs://YOUR_BUCKET_NAME
This sets the permission on the bucket itself. The project-level role gets inherited, but the bucket-level policy can be more restrictive. Setting it on the bucket forces the permission.
Less Common Variations
Uniform vs Fine-Grained Access Control
If your bucket uses Uniform bucket-level access (recommended), IAM is the only way. If it uses Fine-grained, you might also need to set per-object ACLs. Check with:
gsutil bucket get iam gs://YOUR_BUCKET_NAME
If you see a policy, you're on Uniform. If it says "No IAM policies", you're on Fine-grained and need to set ACLs too:
gsutil acl ch -u YOUR_SERVICE_ACCT@PROJECT.iam.gserviceaccount.com:W gs://YOUR_BUCKET_NAME
Cross-Project Access
When your bucket lives in Project A but your service account is in Project B, you must grant access from Project A's side. The service account email stays the same, but the command runs against Project A:
gcloud projects add-iam-policy-binding PROJECT_A \
--member='serviceAccount:SERVICE_ACCT_EMAIL_FROM_B' \
--role='roles/storage.objectAdmin'
Also check that Project B's service account has the Storage Object Admin role in its own project—without it, the call won't even reach Project A's bucket.
Workload Identity Federation
If you're using Workload Identity Federation to access GCS from outside Google Cloud (like AWS or on-prem), the error often means the principal mapping or attribute condition is wrong. The IAM role must include a condition like iam.googleapis.com/attribute.service_account:YOUR_SA_EMAIL. Without it, GCS sees an anonymous caller.
Prevention: Lock It Down Early
Stop needing to fix this again. When you create a bucket, set the IAM policy at creation time:
gsutil mb -p YOUR_PROJECT --iam-policy-file policy.json gs://YOUR_BUCKET_NAME
In policy.json, include a binding for your service account with roles/storage.objectAdmin. This avoids the default bucket policy that allows only project owners.
Also, audit your IAM roles monthly. Use gcloud projects get-iam-policy and look for roles like roles/owner or roles/editor—they're overkill for storage access. Stick to roles/storage.objectAdmin for data, roles/storage.admin only if you need to manage the bucket itself.
One more thing: never use allUsers or allAuthenticatedUsers in production. That's how buckets get pwned. If you need public access, use a signed URL or a load balancer in front.
Was this solution helpful?