Cloud Storage Bucket Access Denied: Fix in 3 Steps
You're getting a 403 on a bucket. Start with IAM roles, then bucket policies, then object ACLs. One of these is blocking you.
Bucket Access Denied? Start here.
You tried to list or read objects from a cloud storage bucket, and you got a 403. What's actually happening here is your request reached the bucket, but the bucket's access control system said no. This isn't a network issue or a credential problem—your key is valid, but the bucket's policy engine rejected the request.
I'll walk you through three fixes, from fastest to most involved. Stop when your issue resolves.
Real-world trigger: This happens when you create a new service account for an app, assign it to a VM, and then try to read from a bucket that was created earlier with different permissions. Or when you migrate data between regions and the new bucket inherits restrictive defaults.
Fix 1 (30 seconds): Check IAM roles
This is the most common cause. Your identity—user, service account, or role—lacks the right IAM permission. Cloud storage systems (GCS, S3, Azure Blob) all use IAM for coarse-grained access.
For Google Cloud Storage:
- Go to IAM & Admin in the console.
- Find your principal (service account email or user).
- Look for the
roles/storage.objectViewerorroles/storage.objectAdminrole on the bucket or the project.
For AWS S3:
- Open IAM → Users (or Roles) → your identity.
- Attach the
AmazonS3ReadOnlyAccesspolicy or an inline policy withs3:GetObjectands3:ListBucket.
For Azure Blob Storage:
- In Storage Account → Access Control (IAM).
- Assign
Storage Blob Data Readerto your managed identity or user.
The reason this fix works fast is IAM roles are evaluated first in the auth chain. If your identity doesn't have the role, the request never reaches the bucket policy.
Fix 2 (5 minutes): Inspect the bucket policy
If IAM looks correct, the bucket itself might have a policy that overrides or denies your access. Bucket policies are evaluated after IAM, and if they contain a Deny for your principal, you're blocked regardless of IAM roles.
For Google Cloud Storage — the bucket policy is a uniform IAM policy, not a separate thing. But check if you've set fine-grained ACLs on the bucket. Go to the bucket's Permissions tab. If ACLs are enabled, they can supersede IAM. Disable fine-grained ACLs under the bucket settings — this forces uniform access control and simplifies debugging.
For AWS S3 — bucket policies are explicit JSON. Find it under the bucket's Permissions tab. Look for Principal elements. Common mistake: the policy grants access to an IAM role ARN but your request comes from a user ARN. Another one: the Condition block uses aws:SourceIp and your IP isn't in the range.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
If you see a Deny statement, that's your culprit. Remove it or add an explicit Allow for your principal. Remember: any Deny in a bucket policy is an absolute block.
For Azure Blob Storage — bucket policies are called container-level RBAC or public access settings. Under Storage Account → Data Storage → Containers, check if public access is allowed. Also check for stored access policies (SAS) that might override.
Fix 3 (15+ minutes): Object ACLs and encryption
If IAM and bucket policy are both permissive but you still get 403, the problem is at the object level. Two things can cause this: object ACLs and encryption key access.
Object ACLs — In GCS and S3, individual objects can have their own ACLs that override bucket-level permissions. This is rare but happens when someone uploads files with explicit ACLs that exclude you. List the object's ACL:
- GCS:
gsutil acl get gs://bucket/object— look forREADERentries for your project or user. - S3:
aws s3api get-object-acl --bucket my-bucket --key object— check forAllUsersorCanonicalUsergrants.
If the object ACL only grants access to a different account, you need to update it. For S3, use aws s3api put-object-acl --bucket my-bucket --key object --grant-read uri=http://acs.amazonaws.com/groups/global/AllUsers — but only if you want public access. Normally you grant access to your CanonicalUser ID.
Encryption key access — This one is sneaky. If the object is encrypted with a customer-managed key (CMK) or a client-side key, and your principal doesn't have kms:Decrypt permissions, you'll get a 403 that looks like an access denied error. The response body often says AccessDenied but the logs show it's a KMS issue.
For S3 with SSE-KMS, check if your IAM role has kms:Decrypt on the key ARN. For GCS with CMEK, grant the cloudkms.cryptoKeyDecrypter role to your service account. You can test this by temporarily disabling encryption (not recommended in prod) to confirm the root cause.
Still stuck? Check the logs
Cloud audit logs (GCP), CloudTrail (AWS), or Storage Analytics logs (Azure) will tell you exactly why the request was denied. Look for authenticationInfo.principalEmail and status.code = 7 (PERMISSION_DENIED) in GCP logs, or errorCode = AccessDenied in AWS CloudTrail. The detail field often says something like "multiple conditions from the policy" or "the ciphertext refers to a different key".
My opinion: Skip object ACLs entirely if you can. Use uniform bucket-level access on GCS and bucket policies on S3. Object ACLs are a legacy feature that makes debugging a nightmare. Modern cloud storage should be IAM-first, ACLs-last.
That's it. Run through these three layers in order. You'll either fix it in 30 seconds or spend 15 minutes on encryption—but you'll know exactly why.
Was this solution helpful?