CloudFront Distribution S3 Origins Reference Existing Buckets
Overview
This check ensures that CloudFront distributions with S3 origins point to S3 buckets that actually exist. When a CloudFront distribution references a bucket that does not exist, it creates a security vulnerability known as a "dangling origin."
Risk
Severity: High
A dangling S3 origin is a serious security risk. If your CloudFront distribution points to a bucket name that does not exist, an attacker could:
- Create the missing bucket in their own AWS account using the exact name your distribution expects
- Serve malicious content through your CloudFront distribution, which would unknowingly fetch from the attacker's bucket
- Poison your CDN cache with harmful or misleading content
- Damage your reputation by hosting inappropriate content under your domain
This vulnerability threatens both the integrity and availability of your application.
Remediation Steps
Prerequisites
You need access to the AWS Console with permissions to view CloudFront distributions and create S3 buckets. If using the CLI, you also need the AWS CLI installed and configured.
Setting up AWS CLI (if needed)
Install the AWS CLI following AWS installation guide.
Configure your credentials:
aws configure
Verify your setup:
aws sts get-caller-identity
AWS Console Method
Step 1: Identify the missing bucket
- Open the CloudFront console
- Click on the distribution ID flagged by Prowler
- Go to the Origins tab
- Find the S3 origin and note the Origin domain (e.g.,
my-bucket-name.s3.us-east-1.amazonaws.com) - The bucket name is the part before
.s3(e.g.,my-bucket-name)
Step 2: Decide your remediation approach
You have two options:
- Option A: Create the missing bucket (if you need this origin)
- Option B: Remove or update the stale origin (if you no longer need it)
Option A: Create the missing bucket
- Open the S3 console
- Click Create bucket
- Enter the exact bucket name from Step 1
- Select the appropriate AWS Region (typically us-east-1 unless specified otherwise)
- Keep Block all public access enabled (recommended)
- Click Create bucket
- Continue to "Secure the bucket with Origin Access Control" below
Option B: Remove or update the stale origin
- In the CloudFront console, select your distribution
- Go to the Origins tab
- Select the problematic origin and click Edit or Delete
- If deleting, first check the Behaviors tab to ensure no behaviors reference this origin
- Update or remove behaviors as needed, then delete the origin
Secure the Bucket with Origin Access Control
After creating the bucket, you should restrict access so only CloudFront can read from it.
- In the CloudFront console, go to Security > Origin access
- Click Create new OAC
- Enter a name (e.g.,
my-bucket-oac) - For Signing behavior, select Sign requests (recommended)
- Click Create
- Edit your distribution's origin to use this OAC
- CloudFront will provide a bucket policy - copy it and apply it to your S3 bucket
AWS CLI (optional)
List distributions and their origins:
aws cloudfront list-distributions \
--region us-east-1 \
--query "DistributionList.Items[*].{Id:Id,Origins:Origins.Items[*].DomainName}" \
--output table
Check if a bucket exists:
aws s3api head-bucket --bucket <bucket-name> --region us-east-1
If the bucket does not exist, you will see an error like 404 Not Found.
Create the missing bucket:
# For us-east-1 (no LocationConstraint needed)
aws s3api create-bucket \
--bucket <bucket-name> \
--region us-east-1
# For other regions, include LocationConstraint
aws s3api create-bucket \
--bucket <bucket-name> \
--region us-east-1 \
--create-bucket-configuration LocationConstraint=us-east-1
Block public access:
aws s3api put-public-access-block \
--bucket <bucket-name> \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
Create Origin Access Control:
aws cloudfront create-origin-access-control \
--origin-access-control-config \
Name="my-bucket-oac",Description="OAC for S3 origin",SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3
Apply bucket policy for CloudFront OAC:
cat > /tmp/bucket-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<bucket-name>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket <bucket-name> \
--policy file:///tmp/bucket-policy.json
Replace <bucket-name>, <account-id>, and <distribution-id> with your actual values.
CloudFormation (optional)
This template creates an S3 bucket with public access blocked and a bucket policy that allows CloudFront access via Origin Access Control.
AWSTemplateFormatVersion: '2010-09-09'
Description: S3 bucket for CloudFront origin with Origin Access Control
Parameters:
BucketName:
Type: String
Description: Name of the S3 bucket (must match the CloudFront origin configuration)
DistributionId:
Type: String
Description: The CloudFront distribution ID that will access this bucket
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
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/${DistributionId}'
Outputs:
BucketName:
Description: Name of the S3 bucket
Value: !Ref S3Bucket
BucketArn:
Description: ARN of the S3 bucket
Value: !GetAtt S3Bucket.Arn
Deploy the stack:
aws cloudformation create-stack \
--stack-name cloudfront-origin-bucket \
--template-body file://template.yaml \
--parameters \
ParameterKey=BucketName,ParameterValue=<bucket-name> \
ParameterKey=DistributionId,ParameterValue=<distribution-id> \
--region us-east-1
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 (must match CloudFront origin configuration)"
type = string
}
variable "cloudfront_distribution_arn" {
description = "ARN of the CloudFront distribution that will access this bucket"
type = string
}
# S3 bucket for CloudFront origin
resource "aws_s3_bucket" "origin" {
bucket = var.bucket_name
}
# Block all public access
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
}
# Bucket policy allowing CloudFront access via OAC
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" = var.cloudfront_distribution_arn
}
}
}
]
})
}
output "bucket_name" {
description = "Name of the created S3 bucket"
value = aws_s3_bucket.origin.id
}
output "bucket_arn" {
description = "ARN of the created S3 bucket"
value = aws_s3_bucket.origin.arn
}
Deploy with Terraform:
terraform init
terraform plan -var="bucket_name=<bucket-name>" -var="cloudfront_distribution_arn=arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
terraform apply -var="bucket_name=<bucket-name>" -var="cloudfront_distribution_arn=arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
Verification
After remediation, verify the fix:
- In the S3 console, confirm the bucket now exists with the expected name
- In the CloudFront console, verify the origin shows a healthy status (no errors)
- Re-run the Prowler check to confirm it passes:
prowler aws --checks cloudfront_distributions_s3_origin_non_existent_bucket
Additional verification commands
Verify bucket exists:
aws s3api head-bucket --bucket <bucket-name> --region us-east-1 && echo "Bucket exists"
Verify public access is blocked:
aws s3api get-public-access-block --bucket <bucket-name> --region us-east-1
Verify bucket policy is applied:
aws s3api get-bucket-policy --bucket <bucket-name> --region us-east-1 --output text | jq .
Test CloudFront distribution:
curl -I https://<distribution-domain-name>/test-object
Additional Resources
- Restricting access to an Amazon S3 origin
- Creating an Origin Access Control
- S3 bucket naming rules
- CloudFront origins overview
Notes
-
Bucket name uniqueness: S3 bucket names are globally unique. If someone else has already created a bucket with your expected name, you will need to update your CloudFront distribution to use a different bucket name.
-
Origin Access Control vs Origin Access Identity: AWS recommends using Origin Access Control (OAC) instead of the legacy Origin Access Identity (OAI). OAC supports additional features like SSE-KMS encryption and is the modern approach.
-
Multiple distributions: If multiple CloudFront distributions reference the same bucket, ensure the bucket policy includes all distribution ARNs in the condition.
-
Cache invalidation: After fixing a dangling origin, you may want to invalidate the CloudFront cache to ensure stale or error responses are cleared:
aws cloudfront create-invalidation --distribution-id <distribution-id> --paths "/*"