What's Actually Happening

AWS S3 returns Access Denied error when attempting to access bucket or objects. The requestor lacks required permissions despite having valid credentials and the resource exists.

The Error You'll See

Access Denied error:

```bash $ aws s3 ls s3://my-bucket/

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

$ aws s3 cp s3://my-bucket/file.txt .

download failed: s3://my-bucket/file.txt to ./file.txt An error occurred (AccessDenied) when calling the GetObject operation: Access Denied ```

SDK error:

```python import boto3 s3 = boto3.client('s3') s3.get_object(Bucket='my-bucket', Key='file.txt')

# botocore.exceptions.ClientError: An error occurred (403) when calling the GetObject operation: Forbidden ```

Console error:

bash
403 Forbidden
You don't have permission to access this bucket.

Why This Happens

  1. 1.Missing IAM permissions - User/role lacks S3 actions
  2. 2.Bucket policy denial - Explicit deny in bucket policy
  3. 3.ACL restrictions - Object ACL blocks access
  4. 4.KMS encryption - Missing KMS key permissions
  5. 5.VPC endpoint policy - Restrictive endpoint policy
  6. 6.Requester Pays - Bucket requires payer account

Step 1: Verify Credentials and Identity

```bash # Check current identity aws sts get-caller-identity

# Check configured credentials aws configure list

# Check default region aws configure get region

# Test basic S3 access aws s3 ls

# Check IAM user/role aws iam get-user aws iam get-role --role-name my-role

# List attached policies aws iam list-attached-user-policies --user-name my-user aws iam list-attached-role-policies --role-name my-role

# Check inline policies aws iam list-user-policies --user-name my-user aws iam list-role-policies --role-name my-role

# Check effective permissions aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:user/my-user \ --action-names s3:GetObject \ --resource-arns arn:aws:s3:::my-bucket/* ```

Step 2: Check IAM Policy Permissions

```bash # Get IAM policy document aws iam get-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Get policy version aws iam get-policy-version \ --policy-arn arn:aws:iam::123456789012:policy/my-policy \ --version-id v1

# Required S3 permissions for common operations: # List buckets: s3:ListAllMyBuckets # List bucket: s3:ListBucket # Get object: s3:GetObject # Put object: s3:PutObject # Delete object: s3:DeleteObject

# Example IAM policy: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket" ], "Resource": "arn:aws:s3:::my-bucket" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::my-bucket/*" } ] }

# Add missing permissions aws iam put-user-policy \ --user-name my-user \ --policy-name s3-access \ --policy-document file://policy.json ```

Step 3: Check Bucket Policy

```bash # Get bucket policy aws s3api get-bucket-policy --bucket my-bucket

# Check for explicit denies # Example bucket policy: { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ], "Condition": { "NotIpAddress": { "aws:SourceIp": "10.0.0.0/8" } } } ] }

# Delete bucket policy if needed aws s3api delete-bucket-policy --bucket my-bucket

# Update bucket policy aws s3api put-bucket-policy \ --bucket my-bucket \ --policy file://bucket-policy.json

# Common bucket policy patterns:

# Allow specific account: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, "Action": "s3:*", "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ] } ] } ```

Step 4: Check Object ACLs

```bash # Get object ACL aws s3api get-object-acl --bucket my-bucket --key file.txt

# Check owner aws s3api get-object-acl --bucket my-bucket --key file.txt \ --query 'Owner.DisplayName'

# Check grants aws s3api get-object-acl --bucket my-bucket --key file.txt \ --query 'Grants'

# Common ACL issue: Object owned by different account # Fix: Update object ACL aws s3api put-object-acl \ --bucket my-bucket \ --key file.txt \ --acl bucket-owner-full-control

# Disable ACLs (recommended) aws s3api put-bucket-ownership-controls \ --bucket my-bucket \ --ownership-controls '{ "Rules": [{ "ObjectOwnership": "BucketOwnerEnforced" }] }'

# Get bucket ACL aws s3api get-bucket-acl --bucket my-bucket ```

Step 5: Check KMS Encryption

```bash # Check bucket encryption aws s3api get-bucket-encryption --bucket my-bucket

# If using KMS: { "ServerSideEncryptionConfiguration": { "Rules": [ { "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "aws:kms", "KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/xxx" } } ] } }

# Check KMS key policy aws kms get-key-policy --key-id xxx --policy-name default

# Required KMS permissions: { "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:GenerateDataKey" ], "Resource": "arn:aws:kms:us-east-1:123456789012:key/xxx" }

# Grant KMS permissions aws kms put-key-policy \ --key-id xxx \ --policy-name default \ --policy file://kms-policy.json

# Check KMS key is enabled aws kms describe-key --key-id xxx ```

Step 6: Check VPC Endpoint Policy

```bash # List VPC endpoints aws ec2 describe-vpc-endpoints --filters "Name=service-name,Values=com.amazonaws.*.s3"

# Get endpoint policy aws ec2 describe-vpc-endpoints --vpc-endpoint-ids vpce-xxx

# Check for restrictive endpoint policy: { "Statement": [ { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": "*", "Condition": { "StringNotEquals": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/AllowedRole" } } } ] }

# Update endpoint policy aws ec2 modify-vpc-endpoint \ --vpc-endpoint-id vpce-xxx \ --policy-document file://endpoint-policy.json

# Full access policy: { "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "*", "Resource": "*" } ] } ```

Step 7: Check Requester Pays Configuration

```bash # Check Requester Pays aws s3api get-bucket-request-payment --bucket my-bucket

# If Requester Pays enabled: { "Payer": "Requester" }

# Access requires payer account aws s3api list-objects-v2 \ --bucket my-bucket \ --request-payer requester

# In SDK: s3.list_objects_v2(Bucket='my-bucket', RequestPayer='requester')

# Disable Requester Pays aws s3api put-bucket-request-payment \ --bucket my-bucket \ --request-payment-configuration Payer=BucketOwner ```

Step 8: Use AWS Policy Simulator

```bash # Simulate access with Policy Simulator # Via AWS Console: IAM > Policy Simulator

# Via CLI: aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:user/my-user \ --action-names s3:GetObject s3:PutObject \ --resource-arns arn:aws:s3:::my-bucket/file.txt

# Check results { "EvaluationResults": [ { "EvalActionName": "s3:GetObject", "EvalResourceName": "arn:aws:s3:::my-bucket/file.txt", "EvalDecision": "allowed" } ] }

# If denied, check for explicit denies aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:user/my-user \ --action-names s3:GetObject \ --resource-arns arn:aws:s3:::my-bucket/* \ --output json | jq '.EvaluationResults[0].MatchedStatements' ```

Step 9: Check Block Public Access Settings

```bash # Check Block Public Access aws s3api get-public-access-block --bucket my-bucket

# Settings: { "BlockPublicAcls": true, "IgnorePublicAcls": true, "BlockPublicPolicy": true, "RestrictPublicBuckets": true }

# These don't cause Access Denied for authenticated requests # But can block public access attempts

# Disable Block Public Access (if intentional) aws s3api delete-public-access-block --bucket my-bucket ```

Step 10: Debug with CloudTrail

```bash # Check CloudTrail for denied events aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=AccessDenied \ --start-time $(date -d '1 hour ago' +%s)000 \ --max-items 10

# Find specific error aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=ResourceName,AttributeValue=my-bucket \ --start-time $(date -d '1 day ago' +%s)000

# Check event details aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=GetBucketPolicy \ --output json | jq '.Events[0].CloudTrailEvent | fromjson'

# Look for: # - errorCode: "AccessDenied" # - errorMessage with details # - userIdentity (who made request) # - resources (what was accessed) ```

AWS S3 Access Denied Checklist

CheckCommandExpected
Identitysts get-caller-identityValid account
IAM policysimulate-principal-policyallowed
Bucket policyget-bucket-policyNo explicit deny
Object ACLget-object-aclHas access
KMS keydescribe-keyKey accessible
VPC endpointdescribe-vpc-endpointsPolicy allows
Requester Paysget-bucket-request-paymentCorrect payer

Verify the Fix

```bash # After fixing permissions

# 1. Test bucket list aws s3 ls s3://my-bucket/ // Lists objects successfully

# 2. Test object download aws s3 cp s3://my-bucket/file.txt . // Downloads successfully

# 3. Test object upload aws s3 cp file.txt s3://my-bucket/ // Uploads successfully

# 4. Verify with policy simulator aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:user/my-user \ --action-names s3:GetObject \ --resource-arns arn:aws:s3:::my-bucket/* // EvalDecision: allowed

# 5. Check CloudTrail for successful events aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=GetObject // No AccessDenied errors ```

  • [Fix AWS S3 Bucket Not Found](/articles/fix-aws-s3-bucket-not-found)
  • [Fix AWS S3 Upload Failed](/articles/fix-aws-s3-upload-failed)
  • [Fix AWS IAM Permission Denied](/articles/fix-aws-iam-permission-denied)