CloudFront OAC for S3 policy

Setting up a CloudFront distribution in front of your S3 bucket that serves public assets is the recommended way to share your web resources globally.

CloudFront distribution with S3 bucket origin

This approach to serving web assets has a variety of benefits related to cost and security:

  • Serve bucket assets over HTTPS: S3 buckets serving websites only support HTTP.
  • Use a custom domain: You must use CloudFront to use a custom domain for serving assets from S3.
  • Avoid a big S3 bill: S3 to CloudFront traffic is free, and you can put a WAF on CloudFront to prevent DDoS attacks and DoW (Denial-of-Wallet) that can drive you bill up, which can't be controlled when users go directly to the S3 bucket.

Watch out: This configuration is referred to as Origin Access Control (OAC), and shouldn't be confused with the now out-of-date Origin Access Identity (OAI) method of securing a S3 bucket. Also note that the bucket should not be configured as a website endpoint, as CloudFront will use the S3 API to access the objects, and not HTTP.

To set up OAC you must configure the CloudFront distribution to sign it's requests to the S3 bucket, and add a resource policy to the bucket:

{
  "Version": "2012-10-17",
  "Statement": {
    "Sid": "AllowCloudFrontServicePrincipalReadOnly",
    "Effect": "Allow",
    "Principal": {
      "Service": "cloudfront.amazonaws.com"
    },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::EXAMPLE-BUCKET/*",
    "Condition": {
      "StringEquals": {
        "aws:SourceArn": "arn:aws:cloudfront::111122223333:distribution/EXAMPLEDISTID"
      }
    }
  }
}

This policy allows only the CloudFront service principal that also has a resource (i.e. distribution) ARN that matches the one specified to get objects from the EXAMPLE-BUCKET bucket. Don't forget the /* at the end of the resource definition - the s3:GetObject action doesn't work on bucket resources.

Similar Conditions

The aws:SourceArn is present in AWS service-to-service requests. In other scenarios, if the ARN doesn't include the account number (e.g. S3 bucket ARNs), you should add the aws:SourceAccount condition to explicitly check it. If you want to check something that is not an AWS service, use the aws:PrincipalArn condition.