Mike Slinn
Mike Slinn

CORS on AWS S3 and Cloudfront

Published 2021-03-21.
Time to read: about 3 minutes.

This article is categorized under AWS.

This post shows how to enable CORS on an AWS S3 bucket with AWS CLI, then modify the bucket’s CloudFront distribution. In preparing this blog post, I found that the AWS S3 CORS documentation needs to be read in conjunction with how AWS CloudFront can be configured to handle CORS.

I used one origin for testing.

Shell
$ ORIGIN=ancientwarmth.com

$ JSON_FILE=cors.json

The CORS configuration for the AWS S3 bucket will be stored in the file pointed to by JSON_FILE.

Define the AWS S3 Bucket CORS Configuration

This configuration (in JSON format) contains 1 rule:

  1. Allow GET HTTP methods from anywhere.
cors.json
{
  "CORSRules": [
    {
      "AllowedHeaders": [],
      "AllowedMethods": [
          "GET"
      ],
      "AllowedOrigins": [
          "*"
      ],
      "ExposeHeaders": []
    }  ]
}

You can read about CORS configuration in the AWS documentation.

Set the AWS S3 Bucket CORS Configuration

It is easy to set the CORS configuration for an AWS S3 bucket using AWS CLI’s aws s3api put-bucket-cors subcommand:

Shell
$ BUCKET=assets.ancientwarmth.com

$ aws s3api put-bucket-cors \
  --bucket $BUCKET \
  --cors-configuration "file://$JSON_FILE"

Test the AWS S3 Bucket CORS Configuration

Now it is time to test the S3 bucket’s CORS configuration using curl. I defined a bash function to peform the test to save typing. You can use it by first copy/pasting the code below into a shell prompt, then calling the function with the proper arguments, as shown. The function requires 3 arguments: the request origin, the URL of an asset in an AWS S3 bucket, and an HTTP method (which must be in UPPPER CASE).

Shell
$ function testCors {
  if [ -z "$1" ]; then echo "Error: No origin was provided"; exit 1; fi
  if [ -z "$2" ]; then echo "Error: No URL to test was provided"; exit 1; fi
  if [ "$3" ]; then METHOD="$3"; else METHOD=GET; fi

  curl -I -X OPTIONS \
    --no-progress-meter \
    -H "Origin: $1" \
    -H "Access-Control-Request-Method: $METHOD" \
    "$2" 2>&1 | \
  grep --color=never 'Access-Control'
}

The JSON file for testing CORS was s3://$BUCKET/testCors.json:

{
 "key1": "value1",
 "key2": "value2"
}

We will know if CORS is set up properly by receiving a header containing Access-Control-Allow-Origin: *.

Shell
$ URL="https://s3.amazonaws.com/$BUCKET/testCors.json"

$ testCors $ORIGIN $URL GET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method 

The origin worked when the bucket is accessed via a GET method sent to its s3.amazonaws.com DNS alias (yay!).

CORScanner (discussed in a previous blog post) reported no issues:

Shell
$ cors -u s3.amazonaws.com/assets.ancientwarmth.com/testCors.json
Starting CORS scan...
Finished CORS scanning... 

CloudFront

I have not worked through the process of using AWS CLI to obtain a JSON object describing the distribution, and then changing some properties and writing it back. So until that happy day comes, here are 2 screen shots of the AWS CloudFront web console showing the settings. The first screen shot shows the Behaviors tab of the top-level details of the assets.ancientwarmth.com CloudFront distribution.

CloudFront / Edit Distribution / Behaviors <br> About to click on <b>Edit</b> (default behavior)
CloudFront / Edit Distribution / Behaviors
About to click on Edit (default behavior)

My application does not require users to upload anything, so everything in the S3 bucket is truly static. Thus I have no need to PUT, POST or DELETE HTTP methods for the AWS S3 content. I have not seen a good explanation of why enabling OPTIONS HTTP methods is necessary, but every person on Stack Overflow who got CORS to work with AWS S3 says this was necessary. With that in mind, I set the following for the next screen shot:

  • Viewer Protocol Policy: Redirect HTTP to HTTPS
  • Allowed HTTP Methods: GET, HEAD, OPTIONS
  • Cached HTTP Methods: Enable OPTIONS
  • Use a cache policy and origin request policy: (default is Use legacy cache settings, which is usually undesirable)
  • Cache Policy: Managed-CachingOptimized
  • Origin Request Policy: Managed-CORS-S3Origin
Editing default CloudFront distribution behavior
Editing default CloudFront distribution behavior

Managed CORS S3 Origin Poligy

AWS CloudFront's managed origin request policy called Managed-CORS-S3Origin includes the headers that enable cross-origin resource sharing (CORS) requests when the origin is an Amazon S3 bucket. This policy's settings are:

  • Query strings included in origin requests: None
  • Headers included in origin requests:
    • Origin
    • Access-Control-Request-Headers
    • Access-Control-Request-Method
  • Cookies included in origin requests: None

Wait or Invalidate

Whenever you make a configuration change to a CloudFront distribution, or the contents change, the distributed assets will not reflect those changes until the next CloudFront invalidation. Automatic invalidations take 20 minutes. You can invalidate manually for near-instant gratification. I use my AWS command-line utilities to invalidate manually:

Shell
$ awsCfS3DistId $BUCKET | awsCfInvalidate

Now the grand finale:

Shell
$ testCors $ORIGIN $URL GET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method 
😁

The presence of the Access-Control-Allow-Origin header indicates that CORS allowed the data file to be transferred from the content server (AWS S3/CloudFront) to the origin server (the command line).