When working with AWS S3 buckets and IAM users, permission issues like AccessDenied
errors can be particularly frustrating. Let's break down why your configuration isn't working and how to fix it.
Your configuration includes:
- An IAM user
my-user
with access keys - A bucket policy attempting to grant
s3:GetObject
permissions - A separate user policy granting
s3:*
permissions
There are several potential issues in your current approach:
// Problematic bucket policy
{
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/my-user"
},
// This format is correct but may not be the most maintainable approach
}
For IAM users, it's generally better to:
- Attach permissions directly to the user (through IAM policies)
- Keep bucket policies for cross-account access or anonymous access
Try this IAM policy attached directly to your user:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
The key improvements in this policy:
- Explicitly includes
ListBucket
permission which is needed for CLI operations - Grants access to both the bucket and its contents
- Uses the modern policy version (2012-10-17)
After applying these changes, verify with:
aws s3 ls s3://my-bucket --profile my-user
aws s3 cp s3://my-bucket/thing.zip . --profile my-user
If you still encounter issues:
- Check CloudTrail for detailed access logs
- Use the IAM Policy Simulator
- Verify your credentials are properly configured in
~/.aws/credentials
// Sample credentials file format
[my-user]
aws_access_key_id = AKIAXXXXXXXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
region = us-west-2
While we recommended using IAM policies for this case, bucket policies are still useful for:
- Cross-account access
- Requiring specific encryption or security headers
- Granting public read access to specific objects
// Example bucket policy for cross-account access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:root"
},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::my-bucket/*"]
}
]
}
Before declaring victory, ensure:
- The IAM user has no explicit deny policies
- The S3 bucket isn't using bucket owner enforcement
- There are no SCPs restricting access at the organization level
- Your credentials haven't expired
When working with AWS S3 buckets, the "Access Denied" error during download operations typically stems from misconfigured permissions across multiple layers. Let's examine the complete authentication chain required for successful S3 access.
For an IAM user to download files from S3, three critical elements must align:
1. IAM user credentials properly configured (~/.aws/credentials)
2. Correct bucket policy permissions
3. Appropriate IAM user policy (if bucket policy doesn't directly reference the user)
The original bucket policy appears correct at first glance, but let's enhance it with additional safeguards:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/my-user"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
While bucket policies work for cross-account access, attaching policies directly to IAM users provides better granular control:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
To confirm your setup, run these AWS CLI commands:
# Check effective permissions
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:user/my-user \
--action-names s3:GetObject s3:ListBucket \
--resource-arns arn:aws:s3:::my-bucket/thing.zip
# Verify bucket policy
aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text | jq .
- Missing s3:ListBucket permission (required for path-style requests)
- Not specifying both bucket and object ARNs in resources
- Version mismatch in policy documents (always use "2012-10-17")
- Conflicting Deny statements in other attached policies
Enable S3 server access logging to track permission issues:
aws s3api put-bucket-logging \
--bucket my-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "my-log-bucket",
"TargetPrefix": "logs/"
}
}'
Examine CloudTrail logs for detailed authentication failures:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetObject \
--start-time $(date -d "-1 hour" +%s) \
--end-time $(date +%s)