Skip to main content

CloudFront Distribution Uses Origin Access Control (OAC) for All S3 Origins

Overview

This check verifies that your CloudFront distributions use Origin Access Control (OAC) when serving content from S3 buckets. OAC ensures that users can only access your S3 content through CloudFront, not by going directly to the S3 bucket URL.

Think of OAC as a secure handshake between CloudFront and S3. Without it, anyone who knows your S3 bucket URL could bypass CloudFront entirely.

Risk

Without OAC, your S3 objects remain publicly accessible outside of CloudFront. This creates several security issues:

  • Data exposure: Attackers can access files directly from S3, bypassing your CloudFront security controls
  • Lost protections: You lose the benefits of AWS WAF, rate limiting, and detailed access logging
  • Cost abuse: Direct S3 access can lead to unexpected data transfer charges
  • Tampering risk: Limited support for signed uploads and SSE-KMS encryption when not using OAC

Remediation Steps

Prerequisites

You need:

  • Access to the AWS Console with permissions to modify CloudFront and S3
  • The name of the affected CloudFront distribution and S3 bucket
Required IAM permissions

Your IAM user or role needs these permissions:

  • cloudfront:CreateOriginAccessControl
  • cloudfront:GetDistribution
  • cloudfront:UpdateDistribution
  • s3:GetBucketPolicy
  • s3:PutBucketPolicy

AWS Console Method

Step 1: Create an Origin Access Control

  1. Open the CloudFront console
  2. In the left menu, click Security then Origin access
  3. Click Create control setting
  4. Enter a name (e.g., my-bucket-oac)
  5. For Signing behavior, select Sign requests (recommended)
  6. For Origin type, select S3
  7. Click Create

Step 2: Attach OAC to your distribution

  1. Go back to Distributions in the CloudFront console
  2. Click your distribution ID to open it
  3. Click the Origins tab
  4. Select your S3 origin and click Edit
  5. Under Origin access, select Origin access control settings (recommended)
  6. Choose the OAC you created in Step 1
  7. Click Save changes
  8. CloudFront will show a banner with a bucket policy. Click Copy policy

Step 3: Update your S3 bucket policy

  1. Open the S3 console
  2. Click on your bucket name
  3. Go to the Permissions tab
  4. Under Bucket policy, click Edit
  5. Paste the policy you copied from CloudFront
  6. Click Save changes

Step 4: Remove public access (if present)

  1. Still on the bucket's Permissions tab
  2. Under Block public access, click Edit
  3. Check Block all public access
  4. Click Save changes and confirm
AWS CLI (optional)

Step 1: Create an Origin Access Control

aws cloudfront create-origin-access-control \
--region us-east-1 \
--origin-access-control-config \
Name=my-bucket-oac,\
Description="OAC for S3 origin",\
SigningProtocol=sigv4,\
SigningBehavior=always,\
OriginAccessControlOriginType=s3

Save the Id from the output. You will need it in the next step.

Step 2: Get your current distribution configuration

aws cloudfront get-distribution-config \
--region us-east-1 \
--id <distribution-id> > dist-config.json

Step 3: Modify the configuration

Edit dist-config.json:

  1. Find the S3 origin in the Origins.Items array
  2. Add or update OriginAccessControlId with the OAC ID from Step 1
  3. Remove any existing S3OriginConfig.OriginAccessIdentity value (set to empty string)
  4. Copy the ETag value and remove the ETag field from the file
  5. Remove the outer Distribution wrapper, keeping only DistributionConfig

Step 4: Update the distribution

aws cloudfront update-distribution \
--region us-east-1 \
--id <distribution-id> \
--if-match <etag-value> \
--distribution-config file://dist-config.json

Step 5: Update the S3 bucket policy

Create a file named bucket-policy.json:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<your-bucket-name>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
}
]
}

Apply the policy:

aws s3api put-bucket-policy \
--region us-east-1 \
--bucket <your-bucket-name> \
--policy file://bucket-policy.json

Step 6: Block public access on the bucket

aws s3api put-public-access-block \
--region us-east-1 \
--bucket <your-bucket-name> \
--public-access-block-configuration \
BlockPublicAcls=true,\
IgnorePublicAcls=true,\
BlockPublicPolicy=true,\
RestrictPublicBuckets=true
CloudFormation (optional)

This template creates a CloudFront distribution with OAC configured for an S3 origin:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFront distribution with Origin Access Control for S3

Parameters:
BucketName:
Type: String
Description: Name of the S3 bucket to use as origin

Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true

OriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub '${BucketName}-oac'
Description: OAC for S3 bucket access
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4

CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
DefaultRootObject: index.html
Origins:
- Id: S3Origin
DomainName: !GetAtt S3Bucket.RegionalDomainName
S3OriginConfig:
OriginAccessIdentity: ''
OriginAccessControlId: !GetAtt OriginAccessControl.Id
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6

BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowCloudFrontServicePrincipal
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub '${S3Bucket.Arn}/*'
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'

Outputs:
DistributionId:
Value: !Ref CloudFrontDistribution
DistributionDomainName:
Value: !GetAtt CloudFrontDistribution.DomainName
OACId:
Value: !GetAtt OriginAccessControl.Id

Deploy the stack:

aws cloudformation deploy \
--region us-east-1 \
--stack-name cloudfront-oac-stack \
--template-file template.yaml \
--parameter-overrides BucketName=my-secure-bucket
Terraform (optional)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}

provider "aws" {
region = "us-east-1"
}

variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}

resource "aws_s3_bucket" "origin" {
bucket = var.bucket_name
}

resource "aws_s3_bucket_public_access_block" "origin" {
bucket = aws_s3_bucket.origin.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

resource "aws_cloudfront_origin_access_control" "oac" {
name = "${var.bucket_name}-oac"
description = "OAC for S3 bucket access"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}

resource "aws_cloudfront_distribution" "cdn" {
enabled = true
default_root_object = "index.html"

origin {
domain_name = aws_s3_bucket.origin.bucket_regional_domain_name
origin_id = "S3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}

default_cache_behavior {
target_origin_id = "S3Origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]

forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}
}

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket_policy" "origin" {
bucket = aws_s3_bucket.origin.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontServicePrincipal"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.origin.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.cdn.arn
}
}
}
]
})
}

output "distribution_id" {
value = aws_cloudfront_distribution.cdn.id
}

output "distribution_domain_name" {
value = aws_cloudfront_distribution.cdn.domain_name
}

output "oac_id" {
value = aws_cloudfront_origin_access_control.oac.id
}

Deploy:

terraform init
terraform apply -var="bucket_name=my-secure-bucket"

Verification

After completing the remediation:

  1. Open the CloudFront console and click on your distribution
  2. Go to the Origins tab
  3. Verify that your S3 origin shows Origin access control with your OAC name
  4. Test that content loads correctly through your CloudFront URL
  5. Confirm that direct S3 bucket URLs return "Access Denied"
CLI verification commands

Check that OAC is attached to the distribution:

aws cloudfront get-distribution \
--region us-east-1 \
--id <distribution-id> \
--query 'Distribution.DistributionConfig.Origins.Items[*].{Origin:Id,OAC:OriginAccessControlId}'

Verify the bucket blocks public access:

aws s3api get-public-access-block \
--region us-east-1 \
--bucket <your-bucket-name>

All four settings should be true.

Additional Resources

Notes

  • Migrating from OAI: If you are currently using the older Origin Access Identity (OAI), AWS recommends migrating to OAC. OAC provides better security and supports additional features like SSE-KMS encryption and PUT/DELETE operations.

  • Multiple origins: If your distribution has multiple S3 origins, you need to create and attach an OAC to each one. You can reuse the same OAC across origins if they have similar access requirements.

  • Distribution update time: After updating the distribution, changes typically propagate within a few minutes, but can take up to 15 minutes to fully deploy across all edge locations.

  • SSE-KMS buckets: If your S3 bucket uses SSE-KMS encryption, you must also grant CloudFront permission to use the KMS key. Add the CloudFront service principal to your KMS key policy.