Fixing Access Denied (403 Forbidden) Error in Amazon S3: Complete Guide
When an SRE encounters an Access Denied (HTTP 403 Forbidden) error in Amazon S3, it signals a critical authorization issue preventing a principal from performing a requested action. This guide provides a direct, action-oriented approach to diagnose and resolve these errors efficiently, ensuring rapid production recovery and robust long-term policy management.
🆑 Symptoms & Diagnosis¶
Identifying the precise error signature is the first step in debugging a 403 Forbidden error. Look for these common indicators in your application logs, CLI output, or CloudTrail events:
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>ABC123</RequestId><HostId>xyz789</HostId></Error>
User: arn:aws:iam::123456789012:user/sre-admin is not authorized to perform: s3:GetObject on resource: arn:aws:s3:::my-bucket/prod/data.txt
CloudTrail log: { "eventName": "s3:GetObject", "errorCode": "AccessDenied", "errorMessage": "Access Denied" }
Root Cause: S3 access denied errors typically stem from an explicit
Denystatement or, more commonly, a missingAllowstatement within the complex authorization hierarchy of IAM policies, bucket policies, ACLs, permissions boundaries, or session policies. Object ownership mismatches or public access blocks can also contribute to these access issues.
🛠️ Solutions¶
Immediate Mitigation: Quick Fix: Grant Object/Bucket Permissions via CLI¶
This approach focuses on granting immediate access for production recovery. While effective for immediate resolution, this should be considered a temporary measure. Always audit and refine policies for least privilege as a permanent solution.
Immediate Mitigation: Quick Fix: Grant Object/Bucket Permissions via CLI
Granting permissions directly via CLI for emergency access. This is a quick workaround, not a long-term strategy for least privilege.
- Identify Caller Identity: Confirm the AWS principal (user, role) making the request that is experiencing the
Access Deniederror usingaws sts get-caller-identity. - Check Current Object ACLs: Review the existing Access Control List for the specific object using
aws s3api get-object-acl. This helps determine if an ACL is blocking access, especially in cross-account scenarios or with differing object ownership. - Update ACL or Bucket Policy: Add the necessary grantee to the object's ACL or update the bucket policy to permit the required action. The
bucket-owner-full-controlcanned ACL is often useful when an object's owner differs from the bucket owner. - Test Access: Verify that the issue is resolved immediately after applying the changes.
# 1. Identify your current AWS caller identity
aws sts get-caller-identity
# 2. Check current object ACL (if applicable)
# This command helps diagnose if an ACL is explicitly denying access or needs adjustment.
aws s3api get-object-acl --bucket my-bucket --key prod/data.txt
# 3. Example: Grant bucket owner full control to an object
# This is commonly used when an object is uploaded by a different AWS account,
# and the bucket owner needs full control.
!!! warning "Review Carefully: This command modifies object ACLs. Ensure you're targeting the correct object and granting the intended permissions. Improper use can inadvertently expose resources or grant excessive privileges."
aws s3api put-object-acl \
--bucket my-bucket \
--key prod/data.txt \
--acl bucket-owner-full-control
# 4. Test access after modification
aws s3 ls s3://my-bucket/prod/data.txt && echo 'SUCCESS: Object is now accessible.'
# If the previous command fails, run with --debug to get more info:
# aws s3 ls s3://my-bucket/prod/data.txt --debug
Best Practice Fix: Permanent Fix: Audit and Update IAM + Bucket Policies¶
For a robust and secure environment, a comprehensive audit and update of IAM and bucket policies are essential to enforce the principle of least privilege and prevent recurrence.
Best Practice Fix: Permanent Fix: Audit and Update IAM + Bucket Policies
A thorough audit and precise updates to IAM and bucket policies for long-term security and compliance.
- Parse CloudTrail Logs: Analyze CloudTrail events to identify the exact
AccessDeniedevents, including the requesting principal and the denied resource/action. This provides the most accurate context for policy evaluation. - Simulate Policy Effects: Use IAM Access Analyzer's
simulate-principal-policyto evaluate the effective permissions before deploying changes. This allows for a dry run of policy adjustments. - Evaluate Permissions Boundaries: Detach or modify any IAM permissions boundaries if they are inadvertently restricting access. Permissions boundaries set the maximum permissions an IAM entity can have.
- Apply Least-Privilege Policy: Implement policies that grant only the necessary permissions, adhering to security best practices.
# 1. Parse CloudTrail logs for AccessDenied events related to S3 GetObject
# Adjust the log group name and time range to match your CloudTrail configuration.
aws logs get-log-events --log-group-name CloudTrail/MyAccount \
--query 'events[?message contains(`"errorCode":"AccessDenied"`) && message contains(`"eventName":"GetObject"`)].message' \
--output json
# 2. Audit IAM effective policy for a principal (e.g., user/sre-admin) on an S3 resource
# Replace the principal ARN and resource ARN with your specific values.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/sre-admin \
--action-names s3:GetObject \
--resource-arn arn:aws:s3:::my-bucket/* \
--output json
# Get the existing bucket policy to understand its current state
aws s3api get-bucket-policy --bucket my-bucket --output json
# 4. Sample IAM policy fix: Grant GetObject, PutObject, and ListBucket on a specific bucket
# Create a JSON file for the IAM policy that grants necessary S3 access.
cat > s3-read-write-my-bucket.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
EOF
# Attach the new IAM policy to a user (e.g., sre-admin)
!!! warning "Review Carefully: This command overwrites an existing user policy if `S3Access` already exists, or creates a new one. Ensure the permissions are appropriate and do not inadvertently revoke other necessary access for the user."
aws iam put-user-policy --user-name sre-admin --policy-name S3Access --policy-document file://s3-read-write-my-bucket.json
# Sample bucket policy fix: Allow a specific IAM user to GetObject
# Create a JSON file for the bucket policy.
cat > bucket-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSREAdminGetObject",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/sre-admin"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
EOF
# Enable or update the bucket policy
!!! warning "Review Carefully: This command overwrites the existing bucket policy. Ensure the new policy includes all necessary existing statements, or you risk breaking other access paths or public access configurations."
aws s3api put-bucket-policy --bucket my-bucket --policy file://bucket-policy.json
Log Parsing for Root Cause (SRE Debug)¶
Deep-diving into logs provides granular details for identifying the exact denying policy or condition. This is crucial for complex authorization scenarios.
- Tail CloudTrail Logs via CLI: Continuously monitor CloudTrail for real-time
AccessDeniedevents to capture transient issues. - Grep for 403 and Policy ARNs: Extract relevant policy ARNs and error codes from CloudTrail or S3 access logs.
- Cross-reference with IAM Simulator: Use the IAM Policy Simulator to validate suspected policies and understand their effective permissions.
# 1. Lookup CloudTrail events for GetObject failures
# This command filters for 'GetObject' events that resulted in 'AccessDenied'.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetObject \
--query 'Events[?CloudTrailEvent.responseElements.errors.code==`AccessDenied`].CloudTrailEvent' \
--output json
# 2. Example: Parsing S3 access logs for 403 errors
# This assumes you have downloaded gzipped S3 access logs to a local directory
# (e.g., '/var/log/s3-access-logs/'). Adjust the path and filename pattern.
# This command identifies unique 403 errors and their corresponding requested paths/actions.
gzcat /var/log/s3-access-logs/2026-02-12-09-*.gz | grep '403' | awk '{print $7, $14}' | sort | uniq -c
📊 Technical Context (Visualized)¶
The Amazon S3 authorization subsystem is a complex evaluation engine. When a principal requests an S3 action (e.g., s3:GetObject), AWS evaluates a hierarchy of identity-based policies (IAM user/role), resource-based policies (bucket policies), Access Control Lists (ACLs), permissions boundaries, and session policies. An explicit Deny statement at any level overrides any Allow statements, resulting in an Access Denied error. If no explicit Allow is found after evaluating all relevant policies, the request is implicitly denied.
graph TD
A[Principal makes S3 Request] --> B{S3 Authorization System};
B --> C{"Evaluate Identity-Based Policies (IAM)"};
C --> D{"Evaluate Resource-Based Policies (Bucket Policy)"};
D -- "> E{"Evaluate Access Control Lists (ACLs)"};
E" --> F{Evaluate Permissions Boundaries & Session Policies};
F -- Explicit Deny Found --> G["Access Denied (403 Forbidden)"];
F -- "No Deny, No Allow" --> G;
F -- All Allows Found --> H[Access Granted];
✅ Verification¶
After implementing any policy changes, verify access immediately using the following CLI commands to confirm the issue is resolved and permissions are correctly applied.
# Verify bucket listing (implies s3:ListBucket permission)
aws s3 ls s3://my-bucket/prod/ && echo 'SUCCESS: Bucket contents listed.'
# Verify object accessibility (implies s3:GetObject permission)
aws s3api head-object --bucket my-bucket --key prod/data.txt && echo 'SUCCESS: Object accessible.'
# Simulate policy to confirm effective permissions post-changes
# This is a critical step to ensure your policy changes result in the expected access.
# Replace the principal ARN and resource ARN with your specific values.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/sre-admin \
--action-names s3:GetObject \
--resource-arn arn:aws:s3:::my-bucket/* \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' \
--output table
⚠️ Prerequisites¶
To effectively diagnose and resolve S3 access issues, ensure your troubleshooting environment has:
- AWS CLI v2.17+ (
aws --version) installed and configured with appropriate credentials. - IAM read access to policies (
iam:SimulatePrincipalPolicy,iam:GetPolicy,iam:ListPolicies) for the principal you are investigating. - S3 permissions on the target bucket, including at least
s3:ListBucketands3:GetObjectAclfor diagnostic purposes. jqfor advanced JSON parsing (optional but highly recommended for CLI output manipulation).- An Admin or SRE IAM role/user with elevated permissions for troubleshooting and applying policy changes.