Problem: S3 Presigned URL Returns 403 Forbidden


Problem: S3 Presigned URL Returns 403 Forbidden

$ aws s3 presign s3://my-private-bucket/private-file.txt --expires-in 600
https://my-private-bucket.s3.amazonaws.com/private-file.txt?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=abcd123456&Expires=1710000000

# Try accessing the URL
$ curl -I "https://my-private-bucket.s3.amazonaws.com/private-file.txt?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=abcd123456&Expires=1710000000"
HTTP/1.1 403 Forbidden
x-amz-request-id: ABCDEF1234567890
x-amz-id-2: abcdefghijklmnopqrstuvwxyz1234567890

Issue:

The presigned URL was generated successfully, but accessing it returns 403 Forbidden. This usually happens because:

  • Wrong IAM permissions – The user creating the URL doesn’t have the right access.
  • Object is missing or in a different bucket – The URL is valid, but the object doesn’t exist.
  • Public Access Block restrictions – Even presigned URLs can be affected by bucket policies.
  • Clock skew issue – The local system time is incorrect, causing an expired signature.

Fix: Verify Permissions, Object Existence, and Time Synchronization

# Step 1: Verify IAM permissions for the user generating the URL
$ aws iam list-attached-user-policies --user-name my-user
{
    "AttachedPolicies": []
}
# If empty, the user lacks permissions.

# Attach the correct policy if missing:
$ aws iam attach-user-policy --user-name my-user --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Verify the policy was applied:
$ aws iam list-attached-user-policies --user-name my-user
{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonS3ReadOnlyAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
        }
    ]
}

# Step 2: Confirm the object exists in S3
$ aws s3 ls s3://my-private-bucket/
2025-02-09 10:05:32   204800  private-file.txt
# If no output, the object is missing.

# Step 3: Check if the bucket has public access restrictions 
# blocking presigned URLs
$ aws s3api get-public-access-block --bucket my-private-bucket
{
    "PublicAccessBlockConfiguration": {
        "RestrictPublicBuckets": true  # TRUE can prevent presigned URLs from working.
    }
}

# If needed, remove the restriction:
$ aws s3api delete-public-access-block --bucket my-private-bucket

# Step 4: Check system time to avoid signature expiration
$ date
Sun Feb 09 12:30:00 UTC 2025

# If incorrect, sync time (Linux example):
$ sudo ntpdate -u pool.ntp.org

# Step 5: Regenerate and test the presigned URL
$ aws s3 presign s3://my-private-bucket/private-file.txt --expires-in 600
https://my-private-bucket.s3.amazonaws.com/private-file.txt?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=abcd123456&Expires=1710000000

$ curl -I "https://my-private-bucket.s3.amazonaws.com/private-file.txt?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=abcd123456&Expires=1710000000"
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 204800

Need AWS Expertise?

If you're looking for guidance on AWS challenges or want to collaborate, feel free to reach out! We'd love to help you tackle your AWS projects. 🚀

Email us at: info@pacificw.com


Image: Gemini

Comments

Popular posts from this blog

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison

The Reasoning Chain in DeepSeek R1: A Glimpse into AI’s Thought Process