I’m using an S3 website endpoint as the origin of my CloudFront distribution. Why am I getting 403 Access Denied errors?

Last updated: 2019-07-30

I'm using an Amazon Simple Storage Service (Amazon S3) bucket as the origin of my Amazon CloudFront distribution. I'm using the S3 static website endpoint as the origin domain name. Why is CloudFront returning 403 Access Denied errors from Amazon S3?

Short Description

To troubleshoot Access Denied errors, you must know if your distribution’s origin domain name is an S3 website endpoint or an S3 REST API endpoint. Follow these steps to determine the endpoint type:

  1. Open the CloudFront console.
  2. Choose your CloudFront distribution, and then choose Distribution Settings.
  3. Choose the Origins tab.
  4. Review the domain name under Origin Domain Name and Path, and then determine the endpoint type based on the format of the domain name.

REST API endpoints use this format:

awsexamplebucket.s3.amazonaws.com

Website endpoints use this format:

awsexamplebucket.s3-website-us-east-1.amazonaws.com

If your distribution is using a REST API endpoint, see I'm using an S3 REST API endpoint as the origin of my CloudFront distribution. Why am I getting 403 Access Denied errors?

If your distribution is using a website endpoint, verify the following requirements to avoid Access Denied errors:

  • Objects in the bucket must be publicly accessible.
  • Objects in the bucket can't be encrypted using AWS Key Management Service (AWS KMS).
  • The bucket policy must allow access to s3:GetObject.
  • If the bucket policy grants public access, the AWS account that owns the bucket must also own the object.
  • The requested objects must exist in the bucket.
  • Amazon S3 block public access must be disabled.
  • If Requester Pays is enabled, the request must include the request-payer parameter.

Resolution

Objects in the bucket must be publicly accessible

A distribution using a website endpoint supports only publicly accessible content. To determine if an object in your S3 bucket is publicly accessible, open the object's URL in a web browser. Or, you can run a curl command on the URL.

The following is an example URL of an S3 object:

http://awsexamplebucket.s3-website-us-east-1.amazonaws.com/index.html

If the web browser or curl command return an Access Denied error, then the object isn't publicly accessible.

Allow public access to the object in one of the following ways:

Objects in the bucket can't be AWS KMS-encrypted

KMS-encrypted objects can't be accessed publicly. Distributions using website endpoints support only publicly accessible content, so you can't serve KMS-encrypted objects from the distribution. This is because API calls to AWS KMS require authentication.

Use one of the following ways to check if an object in your bucket is KMS-encrypted:

  • Use the Amazon S3 console to view the properties of the object. Review the Encryption dialog box. If AWS-KMS is selected, then the object is KMS-encrypted.
  • Run the head-object command using the AWS Command Line Interface (AWS CLI). If the command returns ServerSideEncryption as aws:kms, then the object is KMS-encrypted.

To change the object's encryption settings using the Amazon S3 console, see How Do I Add Encryption to an S3 Object?

To change the object's encryption settings using the AWS CLI, first verify that the object's bucket doesn't have default encryption. If the bucket doesn't have default encryption, run the following AWS CLI command to remove the object's encryption by copying the object over itself.

Warning: Copying the object over itself removes settings for storage-class and website-redirect-location. To maintain these settings in the new object, be sure to explicitly specify storage-class or website-redirect-location values in the copy request. For more information, see Request Headers.

aws s3 cp s3://awsexamplebucket/index.html s3://awsexamplebucket/index.html

The bucket policy must allow access to s3:GetObject

To use a distribution with an S3 website endpoint, your bucket policy must not have a deny statement that blocks public read access to the s3:GetObject action.

Even if you have an explicit allow statement for s3:GetObject in your bucket policy, confirm that there isn't a conflicting explicit deny statement. An explicit deny statement always overrides an explicit allow statement.

Follow these steps to review your bucket policy for s3:GetObject:

1.    Open your S3 bucket from the Amazon S3 console.

2.    Choose the Permissions tab.

3.    Choose Bucket Policy.

4.    Review the bucket policy for statements with "Action": "s3:GetObject" or "Action": "s3:*".

In the following example policy, there's an explicit allow statement for public access to s3:GetObject. However, there's also an explicit deny statement for s3:GetObject that blocks access unless the request is from a specific Amazon Virtual Private Cloud (Amazon VPC).

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "Allow-OAI-Access-To-Bucket",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EAF5XXXXXXXXX"
            },
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::awsexamplebucket/*"
            ]
        },
        {
            "Sid": "Allow-Public-Access-To-Bucket",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::awsexamplebucket/*"
            ]
        },
        {
            "Sid": "Access-to-specific-VPCE-only",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::awsexamplebucket/*"
            ],
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpce": "vpce-1a2b3c4d"
                }
            }
        }
    ]
  }

5.    Modify the bucket policy to remove or edit statements that block public read access to s3:GetObject.

Note: CloudFront caches the results of an Access Denied error for up to 5 minutes. After removing a deny statement from the bucket policy, you can run an invalidation on your distribution to remove the object from the cache.

If the bucket policy grants public access, the AWS account that owns the bucket must also own the object

For a bucket policy to allow public access to objects, the AWS account that owns the bucket must also own the objects. A bucket or object is owned by the account of the AWS Identity and Access Management (IAM) identity that created the bucket or object.

Note: The object-ownership requirement applies to public access granted by a bucket policy. It doesn't apply to public access granted by the object's access control list (ACL).

Follow these steps to check if the bucket and objects have the same owner:

1.    Run this AWS CLI command to get the S3 canonical ID of the bucket owner:

aws s3api list-buckets --query Owner.ID

2.    Run this command to get the S3 canonical ID of the object owner:

Note: This example shows a single object, but you can use the list command to check several objects.

aws s3api list-objects --bucket awsexamplebucket --prefix index.html

3.    If the canonical IDs don't match, then the bucket and object have different owners.

Note: You can also use the Amazon S3 console to check the bucket and object owners. The owners are found in the Permissions tab of the respective bucket or object.

Follow these steps to change the object's owner to the bucket owner:

1.    From the object owner's account, run this command to retrieve the ACL permissions assigned to the object:

aws s3api get-object-acl --bucket awsexamplebucket --key object-name

2.    If the object has bucket-owner-full-control ACL permissions, then skip to step #3. If the object doesn't have bucket-owner-full-control ACL permissions, then run this command from the object owner's account:

aws s3api put-object-acl --bucket awsexamplebucket --key object-name --acl bucket-owner-full-control

3.    Run this command to change the owner of the object by copying the object over itself:

aws s3 cp s3://awsexamplebucket/index.html s3://awsexamplebucket/index.html

The requested objects must exist in the bucket

If a user doesn’t have s3:ListBucket permissions, then the user gets Access Denied errors for missing objects instead of 404 Not Found errors. Run the head-object AWS CLI command to check if an object exists in the bucket.

Note: Confirm that the object request sent to CloudFront matches the S3 object name exactly. S3 object names are case-sensitive. If the request doesn't have the correct object name, then Amazon S3 responds as though the object is missing. To identify which object CloudFront is requesting from Amazon S3, use server access logging.

If the object exists in the bucket, then the Access Denied error isn't masking a 404 Not Found error. Verify other configuration requirements to resolve the Access Denied error.

If the object isn’t in the bucket, then the Access Denied error is masking a 404 Not Found error. Resolve the issue related to the missing object.

Note: We don't recommend enabling public s3:ListBucket access, because it's not a security best practice.

Amazon S3 block public access must be disabled

Confirm that there aren't any Amazon S3 block public access settings applied to the bucket. These settings can override permissions that allow public access. Block public access settings can apply to individual buckets or AWS accounts.

If Requester Pays is enabled, the request must include the request-payer parameter

If the bucket has Requester Pays enabled, then users from other accounts must specify the request-payer parameter when they send requests to the bucket. Otherwise, those users get an Access Denied error.

For GET, HEAD, or POST requests, the user must include the x-amz-request-payer parameter in the header. For REST requests, the user must include the x-amz-request-payer parameter in the request.