Skip to content
v2.274.0

CloudFront Origins for the CDK CloudFront Library ​

This library contains convenience methods for defining origins for a CloudFront distribution. You can use this library to create origins from S3 buckets, Elastic Load Balancing v2 load balancers, or any other domain name.

S3 Bucket ​

An S3 bucket can be used as an origin. An S3 bucket origin can either be configured using a standard S3 bucket or using a S3 bucket that's configured as a website endpoint (see AWS docs for Using an S3 Bucket).

Note: S3Origin has been deprecated. Use S3BucketOrigin for standard S3 origins and S3StaticWebsiteOrigin for static website S3 origins.

Standard S3 Bucket ​

To set up an origin using a standard S3 bucket, use the S3BucketOrigin class. The bucket is handled as a bucket origin and CloudFront's redirect and error handling will be used. It is recommended to use S3BucketOrigin.withOriginAccessControl() to configure OAC for your origin.

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket))
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(S3BucketOrigin.withOriginAccessControl(myBucket)).build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = S3BucketOrigin.WithOriginAccessControl(myBucket) }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket),
	},
})

Note: When you use CloudFront OAC with Amazon S3 bucket origins, you must set Amazon S3 Object Ownership to Bucket owner enforced (the default for new Amazon S3 buckets). If you require ACLs, use the Bucket owner preferred setting to maintain control over objects uploaded via CloudFront.

S3 Bucket Configured as a Website Endpoint ​

To set up an origin using an S3 bucket configured as a website endpoint, use the S3StaticWebsiteOrigin class. When the bucket is configured as a website endpoint, the bucket is treated as an HTTP origin, and the distribution can use built-in S3 redirects and S3 custom error pages.

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: new origins.S3StaticWebsiteOrigin(myBucket) },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.S3StaticWebsiteOrigin(my_bucket))
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(new S3StaticWebsiteOrigin(myBucket)).build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new S3StaticWebsiteOrigin(myBucket) }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewS3StaticWebsiteOrigin(myBucket),
	},
})

Restricting access to a standard S3 Origin ​

CloudFront provides two ways to send authenticated requests to a standard Amazon S3 origin:

  • origin access control (OAC) and
  • origin access identity (OAI)

OAI is considered legacy due to limited functionality and regional limitations, whereas OAC is recommended because it supports all Amazon S3 buckets in all AWS Regions, Amazon S3 server-side encryption with AWS KMS (SSE-KMS), and dynamic requests (PUT and DELETE) to Amazon S3. Additionally, OAC provides stronger security posture with short term credentials, and more frequent credential rotations as compared to OAI. OAI and OAC can be used in conjunction with a bucket that is not public to require that your users access your content using CloudFront URLs and not S3 URLs directly.

See AWS docs on Restricting access to an Amazon S3 Origin for more details.

Note: OAC and OAI can only be used with an regular S3 bucket origin (not a bucket configured as a website endpoint).

The S3BucketOrigin class supports creating a standard S3 origin with OAC, OAI, and no access control (using your bucket access settings) via the withOriginAccessControl(), withOriginAccessIdentity(), and withBucketDefaults() methods respectively.

Setting up a new origin access control (OAC) ​

Setup a standard S3 origin with origin access control as follows:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically creates a S3OriginAccessControl construct
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket)
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(S3BucketOrigin.withOriginAccessControl(myBucket))
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = S3BucketOrigin.WithOriginAccessControl(myBucket)
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket),
	},
})

When creating a standard S3 origin using origins.S3BucketOrigin.withOriginAccessControl(), an Origin Access Control resource is automatically created with the origin type set to s3 and signing behavior set to always.

You can grant read, read versioned, list, write or delete access to the OAC using the originAccessLevels property:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, { originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.READ_VERSIONED, cloudfront.AccessLevel.WRITE, cloudfront.AccessLevel.DELETE],
});
python
my_bucket = s3.Bucket(self, "myBucket")
s3_origin = origins.S3BucketOrigin.with_origin_access_control(my_bucket, origin_access_levels=[cloudfront.AccessLevel.READ, cloudfront.AccessLevel.READ_VERSIONED, cloudfront.AccessLevel.WRITE, cloudfront.AccessLevel.DELETE])
java
Bucket myBucket = new Bucket(this, "myBucket");
IOrigin s3Origin = S3BucketOrigin.withOriginAccessControl(myBucket, S3BucketOriginWithOACProps.builder().originAccessLevels(List.of(AccessLevel.READ, AccessLevel.READ_VERSIONED, AccessLevel.WRITE, AccessLevel.DELETE)).build());
csharp
var myBucket = new Bucket(this, "myBucket");
var s3Origin = S3BucketOrigin.WithOriginAccessControl(myBucket, new S3BucketOriginWithOACProps { OriginAccessLevels = new [] { AccessLevel.READ, AccessLevel.READ_VERSIONED, AccessLevel.WRITE, AccessLevel.DELETE } });
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
s3Origin := origins.S3BucketOrigin_WithOriginAccessControl(myBucket, &S3BucketOriginWithOACProps{
	OriginAccessLevels: []AccessLevel{
		cloudfront.AccessLevel_READ,
		cloudfront.AccessLevel_READ_VERSIONED,
		cloudfront.AccessLevel_WRITE,
		cloudfront.AccessLevel_DELETE,
	},
})

The read versioned permission does contain the read permission, so it's required to set both AccessLevel.READ and AccessLevel.READ_VERSIONED.

For details of list permission, see Setting up OAC with LIST permission.

You can also pass in a custom S3 origin access control:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
const oac = new cloudfront.S3OriginAccessControl(this, 'MyOAC', {
  signing: cloudfront.Signing.SIGV4_NO_OVERRIDE
});
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
    originAccessControl: oac
  }
)
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: s3Origin
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
oac = cloudfront.S3OriginAccessControl(self, "MyOAC",
    signing=cloudfront.Signing.SIGV4_NO_OVERRIDE
)
s3_origin = origins.S3BucketOrigin.with_origin_access_control(my_bucket,
    origin_access_control=oac
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=s3_origin
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
S3OriginAccessControl oac = S3OriginAccessControl.Builder.create(this, "MyOAC")
        .signing(Signing.SIGV4_NO_OVERRIDE)
        .build();
IOrigin s3Origin = S3BucketOrigin.withOriginAccessControl(myBucket, S3BucketOriginWithOACProps.builder()
        .originAccessControl(oac)
        .build());
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(s3Origin)
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
var oac = new S3OriginAccessControl(this, "MyOAC", new S3OriginAccessControlProps {
    Signing = Signing.SIGV4_NO_OVERRIDE
});
var s3Origin = S3BucketOrigin.WithOriginAccessControl(myBucket, new S3BucketOriginWithOACProps {
    OriginAccessControl = oac
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = s3Origin
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
oac := cloudfront.NewS3OriginAccessControl(this, jsii.String("MyOAC"), &S3OriginAccessControlProps{
	Signing: cloudfront.Signing_SIGV4_NO_OVERRIDE(),
})
s3Origin := origins.S3BucketOrigin_WithOriginAccessControl(myBucket, &S3BucketOriginWithOACProps{
	OriginAccessControl: oac,
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
})

An existing S3 origin access control can be imported using the fromOriginAccessControlId method:

ts
const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(this, 'myImportedOAC', 'ABC123ABC123AB');
python
imported_oac = cloudfront.S3OriginAccessControl.from_origin_access_control_id(self, "myImportedOAC", "ABC123ABC123AB")
java
IOriginAccessControl importedOAC = S3OriginAccessControl.fromOriginAccessControlId(this, "myImportedOAC", "ABC123ABC123AB");
csharp
var importedOAC = S3OriginAccessControl.FromOriginAccessControlId(this, "myImportedOAC", "ABC123ABC123AB");
go
importedOAC := cloudfront.S3OriginAccessControl_FromOriginAccessControlId(this, jsii.String("myImportedOAC"), jsii.String("ABC123ABC123AB"))

Note: When you use OAC with S3 bucket origins, the bucket's object ownership must be either set to Bucket owner enforced (default for new S3 buckets) or Bucket owner preferred (only if you require ACLs).

Setting up OAC for an S3 Multi-Region Access Point origin ​

An S3 Multi-Region Access Point origin requires an origin access control that signs with SigV4A, and is configured as a custom origin using the Multi-Region Access Point hostname:

ts
const oac = new cloudfront.S3OriginAccessControl(this, 'MyMrapOAC', {
  signing: cloudfront.Signing.SIGV4A_ALWAYS,
});

new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: new origins.HttpOrigin('mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com', {
      originAccessControlId: oac.originAccessControlId,
    }),
  },
});
python
oac = cloudfront.S3OriginAccessControl(self, "MyMrapOAC",
    signing=cloudfront.Signing.SIGV4A_ALWAYS
)

cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.HttpOrigin("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com",
            origin_access_control_id=oac.origin_access_control_id
        )
    )
)
java
S3OriginAccessControl oac = S3OriginAccessControl.Builder.create(this, "MyMrapOAC")
        .signing(Signing.SIGV4A_ALWAYS)
        .build();

Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(HttpOrigin.Builder.create("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com")
                        .originAccessControlId(oac.getOriginAccessControlId())
                        .build())
                .build())
        .build();
csharp
var oac = new S3OriginAccessControl(this, "MyMrapOAC", new S3OriginAccessControlProps {
    Signing = Signing.SIGV4A_ALWAYS
});

new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new HttpOrigin("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com", new HttpOriginProps {
            OriginAccessControlId = oac.OriginAccessControlId
        })
    }
});
go
oac := cloudfront.NewS3OriginAccessControl(this, jsii.String("MyMrapOAC"), &S3OriginAccessControlProps{
	Signing: cloudfront.Signing_SIGV4A_ALWAYS(),
})

cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewHttpOrigin(jsii.String("mfzwi23gnjvgw.mrap.accesspoint.s3-global.amazonaws.com"), &HttpOriginProps{
			OriginAccessControlId: oac.OriginAccessControlId,
		}),
	},
})

You must also grant CloudFront access in the Multi-Region Access Point policy and in the bucket policy of every underlying bucket. See Restrict access to an Amazon S3 Multi-Region Access Point origin.

Setting up OAC with a SSE-KMS encrypted S3 origin ​

If the objects in the S3 bucket origin are encrypted using server-side encryption with AWS Key Management Service (SSE-KMS), the OAC must have permission to use the KMS key.

Setting up a standard S3 origin using S3BucketOrigin.withOriginAccessControl() will automatically add the statement to the KMS key policy to give the OAC permission to use the KMS key.

ts
import * as kms from 'aws-cdk-lib/aws-kms';

const myKmsKey = new kms.Key(this, 'myKMSKey');
const myBucket = new s3.Bucket(this, 'mySSEKMSEncryptedBucket', {
  encryption: s3.BucketEncryption.KMS,
  encryptionKey: myKmsKey,
  objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED,
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically grants Distribution access to `myKmsKey`
  },
});
python
import aws_cdk.aws_kms as kms


my_kms_key = kms.Key(self, "myKMSKey")
my_bucket = s3.Bucket(self, "mySSEKMSEncryptedBucket",
    encryption=s3.BucketEncryption.KMS,
    encryption_key=my_kms_key,
    object_ownership=s3.ObjectOwnership.BUCKET_OWNER_ENFORCED
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket)
    )
)
java
import software.amazon.awscdk.services.kms.*;


Key myKmsKey = new Key(this, "myKMSKey");
Bucket myBucket = Bucket.Builder.create(this, "mySSEKMSEncryptedBucket")
        .encryption(BucketEncryption.KMS)
        .encryptionKey(myKmsKey)
        .objectOwnership(ObjectOwnership.BUCKET_OWNER_ENFORCED)
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(S3BucketOrigin.withOriginAccessControl(myBucket))
                .build())
        .build();
csharp
using Amazon.CDK.AWS.KMS;


var myKmsKey = new Key(this, "myKMSKey");
var myBucket = new Bucket(this, "mySSEKMSEncryptedBucket", new BucketProps {
    Encryption = BucketEncryption.KMS,
    EncryptionKey = myKmsKey,
    ObjectOwnership = ObjectOwnership.BUCKET_OWNER_ENFORCED
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = S3BucketOrigin.WithOriginAccessControl(myBucket)
    }
});
go
import kms "github.com/aws/aws-cdk-go/awscdk"


myKmsKey := kms.NewKey(this, jsii.String("myKMSKey"))
myBucket := s3.NewBucket(this, jsii.String("mySSEKMSEncryptedBucket"), &BucketProps{
	Encryption: s3.BucketEncryption_KMS,
	EncryptionKey: myKmsKey,
	ObjectOwnership: s3.ObjectOwnership_BUCKET_OWNER_ENFORCED,
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket),
	},
})
Scoping down the key policy ​

I saw this warning message during synth time. What do I do?

text
To avoid a circular dependency between the KMS key, Bucket, and Distribution during the initial deployment, a wildcard is used in the Key policy condition to match all Distribution IDs.
After deploying once, it is strongly recommended to further scope down the policy for best security practices by following the guidance in the "Using OAC for a SSE-KMS encrypted S3 origin" section in the module README.

If the S3 bucket has an encryptionKey defined, S3BucketOrigin.withOriginAccessControl() will automatically add the following policy statement to the KMS key policy to allow CloudFront read-only access (unless otherwise specified in the originAccessLevels property).

json
{
    "Statement": {
        "Effect": "Allow",
        "Principal": {
            "Service": "cloudfront.amazonaws.com"
        },
        "Action": "kms:Decrypt",
        "Resource": "*",
        "Condition": {
            "ArnLike": {
                "AWS:SourceArn": "arn:aws:cloudfront::<account ID>:distribution/*"
            }
        }
    }
}

This policy uses a wildcard to match all distribution IDs in the account instead of referencing the specific distribution ID to resolve the circular dependency. The policy statement is not as scoped down as the example in the AWS CloudFront docs (see SSE-KMS section).

After you have deployed the Distribution, you should follow these steps to only grant permissions to the specific distribution according to AWS best practices:

Step 1. Copy the key policy

Step 2. Use an escape hatch to update the policy statement condition so that

json
  "Condition": {
      "ArnLike": {
          "AWS:SourceArn": "arn:aws:cloudfront::<account ID>:distribution/*"
      }
  }

...becomes...

json
  "Condition": {
      "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
      }
  }

Note the change of condition operator from ArnLike to StringEquals in addition to replacing the wildcard (*) with the distribution ID.

To set the key policy using an escape hatch:

ts
import * as kms from 'aws-cdk-lib/aws-kms';

const kmsKey = new kms.Key(this, 'myKMSKey');
const myBucket = new s3.Bucket(this, 'mySSEKMSEncryptedBucket', {
  encryption: s3.BucketEncryption.KMS,
  encryptionKey: kmsKey,
  objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED,
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket)
  },
});

// Add the following to scope down the key policy
const scopedDownKeyPolicy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111122223333:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": [
                "kms:Decrypt",
                "kms:Encrypt",
                "kms:GenerateDataKey*"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
                }
            }
        }
    ]
};
const cfnKey = (kmsKey.node.defaultChild as kms.CfnKey);
cfnKey.keyPolicy = scopedDownKeyPolicy;
python
import aws_cdk.aws_kms as kms


kms_key = kms.Key(self, "myKMSKey")
my_bucket = s3.Bucket(self, "mySSEKMSEncryptedBucket",
    encryption=s3.BucketEncryption.KMS,
    encryption_key=kms_key,
    object_ownership=s3.ObjectOwnership.BUCKET_OWNER_ENFORCED
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket)
    )
)

# Add the following to scope down the key policy
scoped_down_key_policy = {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::111122223333:root"
        },
        "Action": "kms:*",
        "Resource": "*"
    }, {
        "Effect": "Allow",
        "Principal": {
            "Service": "cloudfront.amazonaws.com"
        },
        "Action": ["kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey*"
        ],
        "Resource": "*",
        "Condition": {
            "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
            }
        }
    }
    ]
}
cfn_key = (kms_key.node.default_child)
cfn_key.key_policy = scoped_down_key_policy
java
import software.amazon.awscdk.services.kms.*;


Key kmsKey = new Key(this, "myKMSKey");
Bucket myBucket = Bucket.Builder.create(this, "mySSEKMSEncryptedBucket")
        .encryption(BucketEncryption.KMS)
        .encryptionKey(kmsKey)
        .objectOwnership(ObjectOwnership.BUCKET_OWNER_ENFORCED)
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(S3BucketOrigin.withOriginAccessControl(myBucket))
                .build())
        .build();

// Add the following to scope down the key policy
Map<String, Object> scopedDownKeyPolicy = Map.of(
        "Version", "2012-10-17",
        "Statement", List.of(Map.of(
                "Effect", "Allow",
                "Principal", Map.of(
                        "AWS", "arn:aws:iam::111122223333:root"),
                "Action", "kms:*",
                "Resource", "*"), Map.of(
                "Effect", "Allow",
                "Principal", Map.of(
                        "Service", "cloudfront.amazonaws.com"),
                "Action", List.of("kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey*"),
                "Resource", "*",
                "Condition", Map.of(
                        "StringEquals", Map.of(
                                "AWS:SourceArn", "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>")))));
CfnKey cfnKey = ((CfnKey)kmsKey.getNode().getDefaultChild());
cfnKey.getKeyPolicy() = scopedDownKeyPolicy;
csharp
using Amazon.CDK.AWS.KMS;


var kmsKey = new Key(this, "myKMSKey");
var myBucket = new Bucket(this, "mySSEKMSEncryptedBucket", new BucketProps {
    Encryption = BucketEncryption.KMS,
    EncryptionKey = kmsKey,
    ObjectOwnership = ObjectOwnership.BUCKET_OWNER_ENFORCED
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = S3BucketOrigin.WithOriginAccessControl(myBucket)
    }
});

// Add the following to scope down the key policy
IDictionary<string, object> scopedDownKeyPolicy = new Dictionary<string, object> {
    { "Version", "2012-10-17" },
    { "Statement", new [] { new Dictionary<string, object> {
        { "Effect", "Allow" },
        { "Principal", new Dictionary<string, string> {
            { "AWS", "arn:aws:iam::111122223333:root" }
        } },
        { "Action", "kms:*" },
        { "Resource", "*" }
    }, new Dictionary<string, object> {
        { "Effect", "Allow" },
        { "Principal", new Dictionary<string, string> {
            { "Service", "cloudfront.amazonaws.com" }
        } },
        { "Action", new [] { "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey*" } },
        { "Resource", "*" },
        { "Condition", new Dictionary<string, IDictionary<string, string>> {
            { "StringEquals", new Dictionary<string, string> {
                { "AWS:SourceArn", "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>" }
            } }
        } }
    } } }
};
var cfnKey = ((CfnKey)kmsKey.Node.DefaultChild);
cfnKey.KeyPolicy = scopedDownKeyPolicy;
go
import kms "github.com/aws/aws-cdk-go/awscdk"


kmsKey := kms.NewKey(this, jsii.String("myKMSKey"))
myBucket := s3.NewBucket(this, jsii.String("mySSEKMSEncryptedBucket"), &BucketProps{
	Encryption: s3.BucketEncryption_KMS,
	EncryptionKey: kmsKey,
	ObjectOwnership: s3.ObjectOwnership_BUCKET_OWNER_ENFORCED,
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket),
	},
})

// Add the following to scope down the key policy
scopedDownKeyPolicy := map[string]interface{}{
	"Version": jsii.String("2012-10-17"),
	"Statement": []interface{}{
		map[string]interface{}{
			"Effect": jsii.String("Allow"),
			"Principal": map[string]*string{
				"AWS": jsii.String("arn:aws:iam::111122223333:root"),
			},
			"Action": jsii.String("kms:*"),
			"Resource": jsii.String("*"),
		},
		map[string]interface{}{
			"Effect": jsii.String("Allow"),
			"Principal": map[string]*string{
				"Service": jsii.String("cloudfront.amazonaws.com"),
			},
			"Action": []*string{
				jsii.String("kms:Decrypt"),
				jsii.String("kms:Encrypt"),
				jsii.String("kms:GenerateDataKey*"),
			},
			"Resource": jsii.String("*"),
			"Condition": map[string]map[string]*string{
				"StringEquals": map[string]*string{
					"AWS:SourceArn": jsii.String("arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"),
				},
			},
		},
	},
}
cfnKey := (kmsKey.Node.defaultChild.(CfnKey))
cfnKey.keyPolicy = scopedDownKeyPolicy

Step 3. Deploy the stack

Tip: Run cdk diff before deploying to verify the changes to your stack.

Step 4. Verify your final key policy includes the following statement after deploying:

json
{
    "Effect": "Allow",
    "Principal": {
        "Service": [
            "cloudfront.amazonaws.com"
        ]
     },
    "Action": [
        "kms:Decrypt",
        "kms:Encrypt",
        "kms:GenerateDataKey*"
    ],
    "Resource": "*",
    "Condition": {
            "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
            }
        }
}
Updating imported key policies ​

If you are using an imported KMS key to encrypt your S3 bucket and want to use OAC, you will need to update the key policy manually to allow CloudFront to use the key. Like most imported resources, CDK apps cannot modify the configuration of imported keys.

After deploying the distribution, add the following policy statement to your key policy to allow CloudFront OAC to access your KMS key for SSE-KMS:

json
{
    "Sid": "AllowCloudFrontServicePrincipalSSE-KMS",
    "Effect": "Allow",
    "Principal": {
        "Service": [
            "cloudfront.amazonaws.com"
        ]
     },
    "Action": [
        "kms:Decrypt",
        "kms:Encrypt",
        "kms:GenerateDataKey*"
    ],
    "Resource": "*",
    "Condition": {
            "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
            }
        }
}

See CloudFront docs on SSE-KMS for more details.

Setting up OAC with imported S3 buckets ​

If you are using an imported bucket for your S3 Origin and want to use OAC, you will need to update the S3 bucket policy manually to allow the OAC to access the S3 origin. Like most imported resources, CDK apps cannot modify the configuration of imported buckets.

After deploying the distribution, add the following policy statement to your S3 bucket to allow CloudFront read-only access (or additional S3 permissions as required):

json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Principal": {
            "Service": "cloudfront.amazonaws.com"
        },
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::<S3 bucket name>/*",
        "Condition": {
            "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/<CloudFront distribution ID>"
            }
        }
    }
}

See CloudFront docs on Giving the origin access control permission to access the S3 bucket for more details.

Note: If your bucket previously used OAI, you will need to manually remove the policy statement that gives the OAI access to your bucket after setting up OAC.

Setting up OAC with LIST permission ​

By default, S3 origin returns 403 Forbidden HTTP response when the requested object does not exist. When you want to receive 404 Not Found, specify AccessLevel.LIST in originAccessLevels to add s3:ListBucket permission in the bucket policy.

This is useful to distinguish between responses blocked by WAF (403) and responses where the file does not exist (404).

ts
const myBucket = new s3.Bucket(this, 'myBucket');
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
  originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST],
});
new cloudfront.Distribution(this, 'distribution', {
  defaultBehavior: {
    origin: s3Origin,
  },
  defaultRootObject: 'index.html', // recommended to specify
});
python
my_bucket = s3.Bucket(self, "myBucket")
s3_origin = origins.S3BucketOrigin.with_origin_access_control(my_bucket,
    origin_access_levels=[cloudfront.AccessLevel.READ, cloudfront.AccessLevel.LIST]
)
cloudfront.Distribution(self, "distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=s3_origin
    ),
    default_root_object="index.html"
)
java
Bucket myBucket = new Bucket(this, "myBucket");
IOrigin s3Origin = S3BucketOrigin.withOriginAccessControl(myBucket, S3BucketOriginWithOACProps.builder()
        .originAccessLevels(List.of(AccessLevel.READ, AccessLevel.LIST))
        .build());
Distribution.Builder.create(this, "distribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(s3Origin)
                .build())
        .defaultRootObject("index.html")
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
var s3Origin = S3BucketOrigin.WithOriginAccessControl(myBucket, new S3BucketOriginWithOACProps {
    OriginAccessLevels = new [] { AccessLevel.READ, AccessLevel.LIST }
});
new Distribution(this, "distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = s3Origin
    },
    DefaultRootObject = "index.html"
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
s3Origin := origins.S3BucketOrigin_WithOriginAccessControl(myBucket, &S3BucketOriginWithOACProps{
	OriginAccessLevels: []AccessLevel{
		cloudfront.AccessLevel_READ,
		cloudfront.AccessLevel_LIST,
	},
})
cloudfront.NewDistribution(this, jsii.String("distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
	DefaultRootObject: jsii.String("index.html"),
})

When the origin is associated to the default behavior, it is highly recommended to specify defaultRootObject distribution property. Without it, the root path https://xxxx.cloudfront.net/ will return the list of the S3 object keys.

Setting up an OAI (legacy) ​

Setup an S3 origin with origin access identity (legacy) as follows:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withOriginAccessIdentity(myBucket) // Automatically creates an OAI
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3BucketOrigin.with_origin_access_identity(my_bucket)
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(S3BucketOrigin.withOriginAccessIdentity(myBucket))
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = S3BucketOrigin.WithOriginAccessIdentity(myBucket)
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessIdentity(myBucket),
	},
})

You can also pass in a custom S3 origin access identity:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
const myOai = new cloudfront.OriginAccessIdentity(this, 'myOAI', {
  comment: 'My custom OAI'
});
const s3Origin = origins.S3BucketOrigin.withOriginAccessIdentity(myBucket, {
  originAccessIdentity: myOai
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: s3Origin
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
my_oai = cloudfront.OriginAccessIdentity(self, "myOAI",
    comment="My custom OAI"
)
s3_origin = origins.S3BucketOrigin.with_origin_access_identity(my_bucket,
    origin_access_identity=my_oai
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=s3_origin
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
OriginAccessIdentity myOai = OriginAccessIdentity.Builder.create(this, "myOAI")
        .comment("My custom OAI")
        .build();
IOrigin s3Origin = S3BucketOrigin.withOriginAccessIdentity(myBucket, S3BucketOriginWithOAIProps.builder()
        .originAccessIdentity(myOai)
        .build());
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(s3Origin)
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
var myOai = new OriginAccessIdentity(this, "myOAI", new OriginAccessIdentityProps {
    Comment = "My custom OAI"
});
var s3Origin = S3BucketOrigin.WithOriginAccessIdentity(myBucket, new S3BucketOriginWithOAIProps {
    OriginAccessIdentity = myOai
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = s3Origin
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
myOai := cloudfront.NewOriginAccessIdentity(this, jsii.String("myOAI"), &OriginAccessIdentityProps{
	Comment: jsii.String("My custom OAI"),
})
s3Origin := origins.S3BucketOrigin_WithOriginAccessIdentity(myBucket, &S3BucketOriginWithOAIProps{
	OriginAccessIdentity: myOai,
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
})

Setting up OAI with imported S3 buckets (legacy) ​

If you are using an imported bucket for your S3 Origin and want to use OAI, you will need to update the S3 bucket policy manually to allow the OAI to access the S3 origin. Like most imported resources, CDK apps cannot modify the configuration of imported buckets.

Add the following policy statement to your S3 bucket to allow the OAI read access:

json
{
    "Version": "2012-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <origin access identity ID>"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<S3 bucket name>/*"
        }
    ]
}

See AWS docs on Giving an origin access identity permission to read files in the Amazon S3 bucket for more details.

Setting up a S3 origin with no origin access control ​

To setup a standard S3 origin with no access control (no OAI nor OAC), use origins.S3BucketOrigin.withBucketDefaults():

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: origins.S3BucketOrigin.withBucketDefaults(myBucket)
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.S3BucketOrigin.with_bucket_defaults(my_bucket)
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(S3BucketOrigin.withBucketDefaults(myBucket))
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = S3BucketOrigin.WithBucketDefaults(myBucket)
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithBucketDefaults(myBucket),
	},
})

Migrating from OAI to OAC ​

If you are currently using OAI for your S3 origin and wish to migrate to OAC, replace the S3Origin construct (deprecated) with S3BucketOrigin.withOriginAccessControl() which automatically creates and sets up an OAC for you.

Existing setup using OAI and S3Origin:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
const s3Origin = new origins.S3Origin(myBucket);
const distribution = new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: s3Origin },
});
python
my_bucket = s3.Bucket(self, "myBucket")
s3_origin = origins.S3Origin(my_bucket)
distribution = cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=s3_origin)
)
java
Bucket myBucket = new Bucket(this, "myBucket");
S3Origin s3Origin = new S3Origin(myBucket);
Distribution distribution = Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(s3Origin).build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
var s3Origin = new S3Origin(myBucket);
var distribution = new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = s3Origin }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
s3Origin := origins.NewS3Origin(myBucket)
distribution := cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
})

Step 1:

To ensure CloudFront doesn't lose access to the bucket during the transition, add a statement to bucket policy to grant OAC access to the S3 origin. Deploy the stack. If you are okay with downtime during the transition, you can skip this step.

Tip: Run cdk diff before deploying to verify the changes to your stack.

ts
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';

const stack = new Stack();
const myBucket = new s3.Bucket(this, 'myBucket');
const s3Origin = new origins.S3Origin(myBucket);
const distribution = new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: s3Origin },
});

// Construct the bucket policy statement
const distributionArn = stack.formatArn(
  {
    service: 'cloudfront',
    region: '',
    resource: 'distribution',
    resourceName: distribution.distributionId,
    arnFormat: cdk.ArnFormat.SLASH_RESOURCE_NAME
  }
);

const cloudfrontSP = new iam.ServicePrincipal('cloudfront.amazonaws.com');

const oacBucketPolicyStatement = new iam.PolicyStatement(
  {
    effect: iam.Effect.ALLOW,
    principals: [cloudfrontSP],
    actions: ['s3:GetObject'],
    resources: [myBucket.arnForObjects('*')],
    conditions: {
      "StringEquals": {
        "AWS:SourceArn": distributionArn
      }
    }
  }
)

// Add statement to bucket policy
myBucket.addToResourcePolicy(oacBucketPolicyStatement);
python
import aws_cdk as cdk
import aws_cdk.aws_iam as iam


stack = Stack()
my_bucket = s3.Bucket(self, "myBucket")
s3_origin = origins.S3Origin(my_bucket)
distribution = cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=s3_origin)
)

# Construct the bucket policy statement
distribution_arn = stack.format_arn(
    service="cloudfront",
    region="",
    resource="distribution",
    resource_name=distribution.distribution_id,
    arn_format=cdk.ArnFormat.SLASH_RESOURCE_NAME
)

cloudfront_sp = iam.ServicePrincipal("cloudfront.amazonaws.com")

oac_bucket_policy_statement = iam.PolicyStatement(
    effect=iam.Effect.ALLOW,
    principals=[cloudfront_sp],
    actions=["s3:GetObject"],
    resources=[my_bucket.arn_for_objects("*")],
    conditions={
        "StringEquals": {
            "AWS:SourceArn": distribution_arn
        }
    }
)

# Add statement to bucket policy
my_bucket.add_to_resource_policy(oac_bucket_policy_statement)
java
import software.amazon.awscdk.*;
import software.amazon.awscdk.services.iam.*;


Stack stack = new Stack();
Bucket myBucket = new Bucket(this, "myBucket");
S3Origin s3Origin = new S3Origin(myBucket);
Distribution distribution = Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(s3Origin).build())
        .build();

// Construct the bucket policy statement
String distributionArn = stack.formatArn(ArnComponents.builder()
        .service("cloudfront")
        .region("")
        .resource("distribution")
        .resourceName(distribution.getDistributionId())
        .arnFormat(ArnFormat.SLASH_RESOURCE_NAME)
        .build());

ServicePrincipal cloudfrontSP = new ServicePrincipal("cloudfront.amazonaws.com");

PolicyStatement oacBucketPolicyStatement = PolicyStatement.Builder.create()
        .effect(Effect.ALLOW)
        .principals(List.of(cloudfrontSP))
        .actions(List.of("s3:GetObject"))
        .resources(List.of(myBucket.arnForObjects("*")))
        .conditions(Map.of(
                "StringEquals", Map.of(
                        "AWS:SourceArn", distributionArn)))
        .build();

// Add statement to bucket policy
myBucket.addToResourcePolicy(oacBucketPolicyStatement);
csharp
using Amazon.CDK;
using Amazon.CDK.AWS.IAM;

var stack = new Stack();
var myBucket = new Bucket(this, "myBucket");
var s3Origin = new S3Origin(myBucket);
var distribution = new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = s3Origin }
});

// Construct the bucket policy statement
var distributionArn = stack.FormatArn(new ArnComponents {
    Service = "cloudfront",
    Region = "",
    Resource = "distribution",
    ResourceName = distribution.DistributionId,
    ArnFormat = ArnFormat.SLASH_RESOURCE_NAME
});

var cloudfrontSP = new ServicePrincipal("cloudfront.amazonaws.com");

var oacBucketPolicyStatement = new PolicyStatement(new PolicyStatementProps {
    Effect = Effect.ALLOW,
    Principals = new [] { cloudfrontSP },
    Actions = new [] { "s3:GetObject" },
    Resources = new [] { myBucket.ArnForObjects("*") },
    Conditions = new Dictionary<string, object> {
        { "StringEquals", new Dictionary<string, string> {
            { "AWS:SourceArn", distributionArn }
        } }
    }
});

// Add statement to bucket policy
myBucket.AddToResourcePolicy(oacBucketPolicyStatement);
go
import cdk "github.com/aws/aws-cdk-go/awscdk"
import "github.com/aws/aws-cdk-go/awscdk"


stack := awscdk.Newstack()
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
s3Origin := origins.NewS3Origin(myBucket)
distribution := cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
})

// Construct the bucket policy statement
distributionArn := stack.FormatArn(&ArnComponents{
	Service: jsii.String("cloudfront"),
	Region: jsii.String(""),
	Resource: jsii.String("distribution"),
	ResourceName: distribution.DistributionId,
	ArnFormat: cdk.ArnFormat_SLASH_RESOURCE_NAME,
})

cloudfrontSP := iam.NewServicePrincipal(jsii.String("cloudfront.amazonaws.com"))

oacBucketPolicyStatement := iam.NewPolicyStatement(&PolicyStatementProps{
	Effect: iam.Effect_ALLOW,
	Principals: []IPrincipal{
		cloudfrontSP,
	},
	Actions: []*string{
		jsii.String("s3:GetObject"),
	},
	Resources: []*string{
		myBucket.ArnForObjects(jsii.String("*")),
	},
	Conditions: map[string]interface{}{
		"StringEquals": map[string]*string{
			"AWS:SourceArn": distributionArn,
		},
	},
})

// Add statement to bucket policy
myBucket.AddToResourcePolicy(oacBucketPolicyStatement)

The following changes will take place:

  1. The bucket policy will be modified to grant the CloudFront distribution access. At this point the bucket policy allows both an OAI and an OAC to access the S3 origin.

Step 2:

Replace S3Origin with S3BucketOrigin.withOriginAccessControl(), which creates an OAC and attaches it to the distribution. You can remove the code from Step 1 which updated the bucket policy, as S3BucketOrigin.withOriginAccessControl() updates the bucket policy automatically with the same statement when defined in the Distribution (no net difference).

Run cdk diff before deploying to verify the changes to your stack.

ts
const bucket = new s3.Bucket(this, 'Bucket');
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(bucket);
const distribution = new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin: s3Origin },
});
python
bucket = s3.Bucket(self, "Bucket")
s3_origin = origins.S3BucketOrigin.with_origin_access_control(bucket)
distribution = cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(origin=s3_origin)
)
java
Bucket bucket = new Bucket(this, "Bucket");
IOrigin s3Origin = S3BucketOrigin.withOriginAccessControl(bucket);
Distribution distribution = Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder().origin(s3Origin).build())
        .build();
csharp
var bucket = new Bucket(this, "Bucket");
var s3Origin = S3BucketOrigin.WithOriginAccessControl(bucket);
var distribution = new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = s3Origin }
});
go
bucket := s3.NewBucket(this, jsii.String("Bucket"))
s3Origin := origins.S3BucketOrigin_WithOriginAccessControl(bucket)
distribution := cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: s3Origin,
	},
})

The following changes will take place:

  1. A AWS::CloudFront::OriginAccessControl resource will be created.
  2. The Origin property of the AWS::CloudFront::Distribution will set OriginAccessControlId to the OAC ID after it is created. It will also set S3OriginConfig to {"OriginAccessIdentity": ""}, which deletes the origin access identity from the existing distribution.
  3. The AWS::CloudFront::CloudFrontOriginAccessIdentity resource will be deleted.

Will migrating from OAI to OAC cause any resource replacement?

No, following the migration steps does not cause any replacement of the existing AWS::CloudFront::Distribution, AWS::S3::Bucket nor AWS::S3::BucketPolicy resources. It will modify the bucket policy, create a AWS::CloudFront::OriginAccessControl resource, and delete the existing AWS::CloudFront::CloudFrontOriginAccessIdentity.

Will migrating from OAI to OAC have any availability implications for my application?

Updates to bucket policies are eventually consistent. Therefore, removing OAI permissions and setting up OAC in the same CloudFormation stack deployment is not recommended as it may cause downtime where CloudFront loses access to the bucket. Following the steps outlined above lowers the risk of downtime as the bucket policy is updated to have both OAI and OAC permissions, then in a subsequent deployment, the OAI permissions are removed.

For more information, see Migrating from origin access identity (OAI) to origin access control (OAC).

Adding Custom Headers ​

You can configure CloudFront to add custom headers to the requests that it sends to your origin. These custom headers enable you to send and gather information from your origin that you don’t get with typical viewer requests. These headers can even be customized for each origin. CloudFront supports custom headers for both for custom and Amazon S3 origins.

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
    customHeaders: {
      Foo: 'bar',
    },
  })},
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket,
        custom_headers={
            "Foo": "bar"
        }
    ))
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(S3BucketOrigin.withOriginAccessControl(myBucket, S3BucketOriginWithOACProps.builder()
                .customHeaders(Map.of(
                        "Foo", "bar"))
                .build())).build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = S3BucketOrigin.WithOriginAccessControl(myBucket, new S3BucketOriginWithOACProps {
        CustomHeaders = new Dictionary<string, string> {
            { "Foo", "bar" }
        }
    }) }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket, &S3BucketOriginWithOACProps{
			CustomHeaders: map[string]*string{
				"Foo": jsii.String("bar"),
			},
		}),
	},
})

ELBv2 Load Balancer ​

An Elastic Load Balancing (ELB) v2 load balancer may be used as an origin. In order for a load balancer to serve as an origin, it must be publicly accessible (internetFacing is true). Both Application and Network load balancers are supported.

ts
declare const vpc: ec2.Vpc;
// Create an application load balancer in a VPC. 'internetFacing' must be 'true'
// for CloudFront to access the load balancer and use it as an origin.
const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', {
  vpc,
  internetFacing: true,
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: new origins.LoadBalancerV2Origin(lb) },
});
python
# vpc: ec2.Vpc

# Create an application load balancer in a VPC. 'internetFacing' must be 'true'
# for CloudFront to access the load balancer and use it as an origin.
lb = elbv2.ApplicationLoadBalancer(self, "LB",
    vpc=vpc,
    internet_facing=True
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.LoadBalancerV2Origin(lb))
)
java
Vpc vpc;

// Create an application load balancer in a VPC. 'internetFacing' must be 'true'
// for CloudFront to access the load balancer and use it as an origin.
ApplicationLoadBalancer lb = ApplicationLoadBalancer.Builder.create(this, "LB")
        .vpc(vpc)
        .internetFacing(true)
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(new LoadBalancerV2Origin(lb)).build())
        .build();
csharp
Vpc vpc;

// Create an application load balancer in a VPC. 'internetFacing' must be 'true'
// for CloudFront to access the load balancer and use it as an origin.
var lb = new ApplicationLoadBalancer(this, "LB", new ApplicationLoadBalancerProps {
    Vpc = vpc,
    InternetFacing = true
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new LoadBalancerV2Origin(lb) }
});
go
var vpc Vpc

// Create an application load balancer in a VPC. 'internetFacing' must be 'true'
// for CloudFront to access the load balancer and use it as an origin.
lb := elbv2.NewApplicationLoadBalancer(this, jsii.String("LB"), &ApplicationLoadBalancerProps{
	Vpc: Vpc,
	InternetFacing: jsii.Boolean(true),
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewLoadBalancerV2Origin(lb),
	},
})

The origin can also be customized to respond on different ports, have different connection properties, etc.

ts
declare const loadBalancer: elbv2.ApplicationLoadBalancer;
const origin = new origins.LoadBalancerV2Origin(loadBalancer, {
  connectionAttempts: 3,
  connectionTimeout: Duration.seconds(5),
  readTimeout: Duration.seconds(45),
  responseCompletionTimeout: Duration.seconds(120),
  keepaliveTimeout: Duration.seconds(45),
  protocolPolicy: cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
});
python
# load_balancer: elbv2.ApplicationLoadBalancer

origin = origins.LoadBalancerV2Origin(load_balancer,
    connection_attempts=3,
    connection_timeout=Duration.seconds(5),
    read_timeout=Duration.seconds(45),
    response_completion_timeout=Duration.seconds(120),
    keepalive_timeout=Duration.seconds(45),
    protocol_policy=cloudfront.OriginProtocolPolicy.MATCH_VIEWER
)
java
ApplicationLoadBalancer loadBalancer;

LoadBalancerV2Origin origin = LoadBalancerV2Origin.Builder.create(loadBalancer)
        .connectionAttempts(3)
        .connectionTimeout(Duration.seconds(5))
        .readTimeout(Duration.seconds(45))
        .responseCompletionTimeout(Duration.seconds(120))
        .keepaliveTimeout(Duration.seconds(45))
        .protocolPolicy(OriginProtocolPolicy.MATCH_VIEWER)
        .build();
csharp
ApplicationLoadBalancer loadBalancer;

var origin = new LoadBalancerV2Origin(loadBalancer, new LoadBalancerV2OriginProps {
    ConnectionAttempts = 3,
    ConnectionTimeout = Duration.Seconds(5),
    ReadTimeout = Duration.Seconds(45),
    ResponseCompletionTimeout = Duration.Seconds(120),
    KeepaliveTimeout = Duration.Seconds(45),
    ProtocolPolicy = OriginProtocolPolicy.MATCH_VIEWER
});
go
var loadBalancer ApplicationLoadBalancer

origin := origins.NewLoadBalancerV2Origin(loadBalancer, &LoadBalancerV2OriginProps{
	ConnectionAttempts: jsii.Number(3),
	ConnectionTimeout: awscdk.Duration_Seconds(jsii.Number(5)),
	ReadTimeout: awscdk.Duration_*Seconds(jsii.Number(45)),
	ResponseCompletionTimeout: awscdk.Duration_*Seconds(jsii.Number(120)),
	KeepaliveTimeout: awscdk.Duration_*Seconds(jsii.Number(45)),
	ProtocolPolicy: cloudfront.OriginProtocolPolicy_MATCH_VIEWER,
})

Note that readTimeout and keepaliveTimeout are governed by two separate CloudFront service quotas: Response timeout per origin, which defaults to 1-120 seconds, and Keep-alive timeout per origin, which defaults to 1-300 seconds. Neither is a hard limit. A value above the default requires an approved quota increase in the target account, and without one CloudFront rejects it at deploy time.

From an HTTP endpoint ​

Origins can also be created from any other HTTP endpoint, given the domain name, and optionally, other origin properties.

ts
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: new origins.HttpOrigin('www.example.com') },
});
python
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.HttpOrigin("www.example.com"))
)
java
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(new HttpOrigin("www.example.com")).build())
        .build();
csharp
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new HttpOrigin("www.example.com") }
});
go
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewHttpOrigin(jsii.String("www.example.com")),
	},
})

You can specify the IP address type for connecting to the origin:

ts
const origin = new origins.HttpOrigin('www.example.com', {
  ipAddressType: cloudfront.OriginIpAddressType.IPV6, // IPv4, IPv6, or DUALSTACK
});

new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin },
});
python
origin = origins.HttpOrigin("www.example.com",
    ip_address_type=cloudfront.OriginIpAddressType.IPV6
)

cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(origin=origin)
)
java
HttpOrigin origin = HttpOrigin.Builder.create("www.example.com")
        .ipAddressType(OriginIpAddressType.IPV6)
        .build();

Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder().origin(origin).build())
        .build();
csharp
var origin = new HttpOrigin("www.example.com", new HttpOriginProps {
    IpAddressType = OriginIpAddressType.IPV6
});

new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = origin }
});
go
origin := origins.NewHttpOrigin(jsii.String("www.example.com"), &HttpOriginProps{
	IpAddressType: cloudfront.OriginIpAddressType_IPV6,
})

cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: *Origin,
	},
})

The ipAddressType property allows you to specify whether CloudFront should use IPv4, IPv6, or both (dual-stack) when connecting to your origin.

The origin can be customized with timeout settings to handle different response scenarios:

ts
new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: {
    origin: new origins.HttpOrigin('api.example.com', {
      readTimeout: Duration.seconds(60),
      responseCompletionTimeout: Duration.seconds(120),
      keepaliveTimeout: Duration.seconds(45),
    }),
  },
});
python
cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.HttpOrigin("api.example.com",
            read_timeout=Duration.seconds(60),
            response_completion_timeout=Duration.seconds(120),
            keepalive_timeout=Duration.seconds(45)
        )
    )
)
java
Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(HttpOrigin.Builder.create("api.example.com")
                        .readTimeout(Duration.seconds(60))
                        .responseCompletionTimeout(Duration.seconds(120))
                        .keepaliveTimeout(Duration.seconds(45))
                        .build())
                .build())
        .build();
csharp
new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new HttpOrigin("api.example.com", new HttpOriginProps {
            ReadTimeout = Duration.Seconds(60),
            ResponseCompletionTimeout = Duration.Seconds(120),
            KeepaliveTimeout = Duration.Seconds(45)
        })
    }
});
go
cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewHttpOrigin(jsii.String("api.example.com"), &HttpOriginProps{
			ReadTimeout: awscdk.Duration_Seconds(jsii.Number(60)),
			ResponseCompletionTimeout: awscdk.Duration_*Seconds(jsii.Number(120)),
			KeepaliveTimeout: awscdk.Duration_*Seconds(jsii.Number(45)),
		}),
	},
})

The responseCompletionTimeout property specifies the time that a request from CloudFront to the origin can stay open and wait for a response. If the complete response isn't received from the origin by this time, CloudFront ends the connection. Valid values are 1-3600 seconds, and if set, the value must be equal to or greater than the readTimeout value.

See the documentation of aws-cdk-lib/aws-cloudfront for more information.

VPC origins ​

You can use CloudFront to deliver content from applications that are hosted in your virtual private cloud (VPC) private subnets. You can use Application Load Balancers (ALBs), Network Load Balancers (NLBs), and EC2 instances in private subnets as VPC origins.

Learn more about Restrict access with VPC origins.

From an Application Load Balancer ​

An Application Load Balancer (ALB) can be used as a VPC origin. It is not needed to be publicly accessible.

ts
// Creates a distribution from an Application Load Balancer
declare const vpc: ec2.Vpc;
// Create an application load balancer in a VPC. 'internetFacing' can be 'false'.
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
  vpc,
  internetFacing: false,
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: origins.VpcOrigin.withApplicationLoadBalancer(alb) },
});
python
# Creates a distribution from an Application Load Balancer
# vpc: ec2.Vpc

# Create an application load balancer in a VPC. 'internetFacing' can be 'false'.
alb = elbv2.ApplicationLoadBalancer(self, "ALB",
    vpc=vpc,
    internet_facing=False,
    vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_ISOLATED)
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.VpcOrigin.with_application_load_balancer(alb))
)
java
// Creates a distribution from an Application Load Balancer
Vpc vpc;

// Create an application load balancer in a VPC. 'internetFacing' can be 'false'.
ApplicationLoadBalancer alb = ApplicationLoadBalancer.Builder.create(this, "ALB")
        .vpc(vpc)
        .internetFacing(false)
        .vpcSubnets(SubnetSelection.builder().subnetType(SubnetType.PRIVATE_ISOLATED).build())
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(VpcOrigin.withApplicationLoadBalancer(alb)).build())
        .build();
csharp
// Creates a distribution from an Application Load Balancer
Vpc vpc;

// Create an application load balancer in a VPC. 'internetFacing' can be 'false'.
var alb = new ApplicationLoadBalancer(this, "ALB", new ApplicationLoadBalancerProps {
    Vpc = vpc,
    InternetFacing = false,
    VpcSubnets = new SubnetSelection { SubnetType = SubnetType.PRIVATE_ISOLATED }
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = VpcOrigin.WithApplicationLoadBalancer(alb) }
});
go
// Creates a distribution from an Application Load Balancer
var vpc Vpc

// Create an application load balancer in a VPC. 'internetFacing' can be 'false'.
alb := elbv2.NewApplicationLoadBalancer(this, jsii.String("ALB"), &ApplicationLoadBalancerProps{
	Vpc: Vpc,
	InternetFacing: jsii.Boolean(false),
	VpcSubnets: &SubnetSelection{
		SubnetType: ec2.SubnetType_PRIVATE_ISOLATED,
	},
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.VpcOrigin_WithApplicationLoadBalancer(alb),
	},
})

From a Network Load Balancer ​

A Network Load Balancer (NLB) can also be use as a VPC origin. It is not needed to be publicly accessible.

  • A Network Load Balancer must have a security group attached to it.
  • Dual-stack Network Load Balancers and Network Load Balancers with TLS listeners can't be added as origins.
ts
// Creates a distribution from a Network Load Balancer
declare const vpc: ec2.Vpc;
// Create a network load balancer in a VPC. 'internetFacing' can be 'false'.
const nlb = new elbv2.NetworkLoadBalancer(this, 'NLB', {
  vpc,
  internetFacing: false,
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
  securityGroups: [new ec2.SecurityGroup(this, 'NLB-SG', { vpc })],
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: origins.VpcOrigin.withNetworkLoadBalancer(nlb) },
});
python
# Creates a distribution from a Network Load Balancer
# vpc: ec2.Vpc

# Create a network load balancer in a VPC. 'internetFacing' can be 'false'.
nlb = elbv2.NetworkLoadBalancer(self, "NLB",
    vpc=vpc,
    internet_facing=False,
    vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_ISOLATED),
    security_groups=[ec2.SecurityGroup(self, "NLB-SG", vpc=vpc)]
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.VpcOrigin.with_network_load_balancer(nlb))
)
java
// Creates a distribution from a Network Load Balancer
Vpc vpc;

// Create a network load balancer in a VPC. 'internetFacing' can be 'false'.
NetworkLoadBalancer nlb = NetworkLoadBalancer.Builder.create(this, "NLB")
        .vpc(vpc)
        .internetFacing(false)
        .vpcSubnets(SubnetSelection.builder().subnetType(SubnetType.PRIVATE_ISOLATED).build())
        .securityGroups(List.of(SecurityGroup.Builder.create(this, "NLB-SG").vpc(vpc).build()))
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(VpcOrigin.withNetworkLoadBalancer(nlb)).build())
        .build();
csharp
// Creates a distribution from a Network Load Balancer
Vpc vpc;

// Create a network load balancer in a VPC. 'internetFacing' can be 'false'.
var nlb = new NetworkLoadBalancer(this, "NLB", new NetworkLoadBalancerProps {
    Vpc = vpc,
    InternetFacing = false,
    VpcSubnets = new SubnetSelection { SubnetType = SubnetType.PRIVATE_ISOLATED },
    SecurityGroups = new [] { new SecurityGroup(this, "NLB-SG", new SecurityGroupProps { Vpc = vpc }) }
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = VpcOrigin.WithNetworkLoadBalancer(nlb) }
});
go
// Creates a distribution from a Network Load Balancer
var vpc Vpc

// Create a network load balancer in a VPC. 'internetFacing' can be 'false'.
nlb := elbv2.NewNetworkLoadBalancer(this, jsii.String("NLB"), &NetworkLoadBalancerProps{
	Vpc: Vpc,
	InternetFacing: jsii.Boolean(false),
	VpcSubnets: &SubnetSelection{
		SubnetType: ec2.SubnetType_PRIVATE_ISOLATED,
	},
	SecurityGroups: []ISecurityGroup{
		ec2.NewSecurityGroup(this, jsii.String("NLB-SG"), &SecurityGroupProps{
			Vpc: *Vpc,
		}),
	},
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.VpcOrigin_WithNetworkLoadBalancer(nlb),
	},
})

From an EC2 instance ​

An EC2 instance can also be used directly as a VPC origin. It can be in a private subnet.

ts
// Creates a distribution from an EC2 instance
declare const vpc: ec2.Vpc;
// Create an EC2 instance in a VPC. 'subnetType' can be private.
const instance = new ec2.Instance(this, 'Instance', {
  vpc,
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO),
  machineImage: ec2.MachineImage.latestAmazonLinux2023(),
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: { origin: origins.VpcOrigin.withEc2Instance(instance) },
});
python
# Creates a distribution from an EC2 instance
# vpc: ec2.Vpc

# Create an EC2 instance in a VPC. 'subnetType' can be private.
instance = ec2.Instance(self, "Instance",
    vpc=vpc,
    instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.MICRO),
    machine_image=ec2.MachineImage.latest_amazon_linux2023(),
    vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS)
)
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.VpcOrigin.with_ec2_instance(instance))
)
java
// Creates a distribution from an EC2 instance
Vpc vpc;

// Create an EC2 instance in a VPC. 'subnetType' can be private.
Instance instance = Instance.Builder.create(this, "Instance")
        .vpc(vpc)
        .instanceType(InstanceType.of(InstanceClass.BURSTABLE3, InstanceSize.MICRO))
        .machineImage(MachineImage.latestAmazonLinux2023())
        .vpcSubnets(SubnetSelection.builder().subnetType(SubnetType.PRIVATE_WITH_EGRESS).build())
        .build();
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder().origin(VpcOrigin.withEc2Instance(instance)).build())
        .build();
csharp
// Creates a distribution from an EC2 instance
Vpc vpc;

// Create an EC2 instance in a VPC. 'subnetType' can be private.
var instance = new Instance(this, "Instance", new InstanceProps {
    Vpc = vpc,
    InstanceType = InstanceType.Of(InstanceClass.BURSTABLE3, InstanceSize.MICRO),
    MachineImage = MachineImage.LatestAmazonLinux2023(),
    VpcSubnets = new SubnetSelection { SubnetType = SubnetType.PRIVATE_WITH_EGRESS }
});
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = VpcOrigin.WithEc2Instance(instance) }
});
go
// Creates a distribution from an EC2 instance
var vpc Vpc

// Create an EC2 instance in a VPC. 'subnetType' can be private.
instance := ec2.NewInstance(this, jsii.String("Instance"), &InstanceProps{
	Vpc: Vpc,
	InstanceType: ec2.InstanceType_Of(ec2.InstanceClass_BURSTABLE3, ec2.InstanceSize_MICRO),
	MachineImage: ec2.MachineImage_LatestAmazonLinux2023(),
	VpcSubnets: &SubnetSelection{
		SubnetType: ec2.SubnetType_PRIVATE_WITH_EGRESS,
	},
})
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.VpcOrigin_WithEc2Instance(instance),
	},
})

Restrict traffic coming to the VPC origin ​

You may need to update the security group for your VPC private origin (Application Load Balancer, Network Load Balancer, or EC2 instance) to explicitly allow the traffic from your VPC origins.

The CloudFront managed prefix list ​

You can allow the traffic from the CloudFront managed prefix list named com.amazonaws.global.cloudfront.origin-facing. For more information, see Use an AWS-managed prefix list.

ts
declare const alb: elbv2.ApplicationLoadBalancer;

const cfOriginFacing = ec2.PrefixList.fromLookup(this, 'CloudFrontOriginFacing', {
  prefixListName: 'com.amazonaws.global.cloudfront.origin-facing',
});
alb.connections.allowFrom(cfOriginFacing, ec2.Port.HTTP);
python
# alb: elbv2.ApplicationLoadBalancer


cf_origin_facing = ec2.PrefixList.from_lookup(self, "CloudFrontOriginFacing",
    prefix_list_name="com.amazonaws.global.cloudfront.origin-facing"
)
alb.connections.allow_from(cf_origin_facing, ec2.Port.HTTP)
java
ApplicationLoadBalancer alb;


IPrefixList cfOriginFacing = PrefixList.fromLookup(this, "CloudFrontOriginFacing", PrefixListLookupOptions.builder()
        .prefixListName("com.amazonaws.global.cloudfront.origin-facing")
        .build());
alb.connections.allowFrom(cfOriginFacing, Port.HTTP);
csharp
ApplicationLoadBalancer alb;


var cfOriginFacing = PrefixList.FromLookup(this, "CloudFrontOriginFacing", new PrefixListLookupOptions {
    PrefixListName = "com.amazonaws.global.cloudfront.origin-facing"
});
alb.Connections.AllowFrom(cfOriginFacing, Port.HTTP);
go
var alb ApplicationLoadBalancer


cfOriginFacing := ec2.PrefixList_FromLookup(this, jsii.String("CloudFrontOriginFacing"), &PrefixListLookupOptions{
	PrefixListName: jsii.String("com.amazonaws.global.cloudfront.origin-facing"),
})
alb.Connections.AllowFrom(cfOriginFacing, ec2.Port_HTTP())

The VPC origin service security group ​

VPC origin will create a security group named CloudFront-VPCOrigins-Service-SG. It can be further restricted to allow only traffic from your VPC origins.

The id of the security group is not provided by CloudFormation currently. You can retrieve it dynamically using a custom resource.

ts
import * as cr from 'aws-cdk-lib/custom-resources';

declare const vpc: ec2.Vpc;
declare const distribution: cloudfront.Distribution;
declare const alb: elbv2.ApplicationLoadBalancer;

// Call ec2:DescribeSecurityGroups API to retrieve the VPC origins security group.
const getSg = new cr.AwsCustomResource(this, 'GetSecurityGroup', {
  onCreate: {
    service: 'ec2',
    action: 'describeSecurityGroups',
    parameters: {
      Filters: [
        { Name: 'vpc-id', Values: [vpc.vpcId] },
        { Name: 'group-name', Values: ['CloudFront-VPCOrigins-Service-SG'] },
      ],
    },
    physicalResourceId: cr.PhysicalResourceId.of('CloudFront-VPCOrigins-Service-SG'),
  },
  policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: ['*'] }),
});
// The security group may be available after the distributon is deployed
getSg.node.addDependency(distribution);
const sgVpcOrigins = ec2.SecurityGroup.fromSecurityGroupId(
  this,
  'VpcOriginsSecurityGroup',
  getSg.getResponseField('SecurityGroups.0.GroupId'),
);
// Allow connections from the security group
alb.connections.allowFrom(sgVpcOrigins, ec2.Port.HTTP);
python
import aws_cdk.custom_resources as cr

# vpc: ec2.Vpc
# distribution: cloudfront.Distribution
# alb: elbv2.ApplicationLoadBalancer


# Call ec2:DescribeSecurityGroups API to retrieve the VPC origins security group.
get_sg = cr.AwsCustomResource(self, "GetSecurityGroup",
    on_create=cr.AwsSdkCall(
        service="ec2",
        action="describeSecurityGroups",
        parameters={
            "Filters": [{"Name": "vpc-id", "Values": [vpc.vpc_id]}, {"Name": "group-name", "Values": ["CloudFront-VPCOrigins-Service-SG"]}
            ]
        },
        physical_resource_id=cr.PhysicalResourceId.of("CloudFront-VPCOrigins-Service-SG")
    ),
    policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=["*"])
)
# The security group may be available after the distributon is deployed
get_sg.node.add_dependency(distribution)
sg_vpc_origins = ec2.SecurityGroup.from_security_group_id(self, "VpcOriginsSecurityGroup",
    get_sg.get_response_field("SecurityGroups.0.GroupId"))
# Allow connections from the security group
alb.connections.allow_from(sg_vpc_origins, ec2.Port.HTTP)
java
import software.amazon.awscdk.customresources.*;

Vpc vpc;
Distribution distribution;
ApplicationLoadBalancer alb;


// Call ec2:DescribeSecurityGroups API to retrieve the VPC origins security group.
AwsCustomResource getSg = AwsCustomResource.Builder.create(this, "GetSecurityGroup")
        .onCreate(AwsSdkCall.builder()
                .service("ec2")
                .action("describeSecurityGroups")
                .parameters(Map.of(
                        "Filters", List.of(Map.of("Name", "vpc-id", "Values", List.of(vpc.getVpcId())), Map.of("Name", "group-name", "Values", List.of("CloudFront-VPCOrigins-Service-SG")))))
                .physicalResourceId(PhysicalResourceId.of("CloudFront-VPCOrigins-Service-SG"))
                .build())
        .policy(AwsCustomResourcePolicy.fromSdkCalls(SdkCallsPolicyOptions.builder().resources(List.of("*")).build()))
        .build();
// The security group may be available after the distributon is deployed
getSg.node.addDependency(distribution);
ISecurityGroup sgVpcOrigins = SecurityGroup.fromSecurityGroupId(this, "VpcOriginsSecurityGroup", getSg.getResponseField("SecurityGroups.0.GroupId"));
// Allow connections from the security group
alb.connections.allowFrom(sgVpcOrigins, Port.HTTP);
csharp
using Amazon.CDK.CustomResources;

Vpc vpc;
Distribution distribution;
ApplicationLoadBalancer alb;


// Call ec2:DescribeSecurityGroups API to retrieve the VPC origins security group.
var getSg = new AwsCustomResource(this, "GetSecurityGroup", new AwsCustomResourceProps {
    OnCreate = new AwsSdkCall {
        Service = "ec2",
        Action = "describeSecurityGroups",
        Parameters = new Dictionary<string, IDictionary<string, object>[]> {
            { "Filters", new [] { new Struct { Name = "vpc-id", Values = new [] { vpc.VpcId } }, new Struct { Name = "group-name", Values = new [] { "CloudFront-VPCOrigins-Service-SG" } } } }
        },
        PhysicalResourceId = PhysicalResourceId.Of("CloudFront-VPCOrigins-Service-SG")
    },
    Policy = AwsCustomResourcePolicy.FromSdkCalls(new SdkCallsPolicyOptions { Resources = new [] { "*" } })
});
// The security group may be available after the distributon is deployed
getSg.Node.AddDependency(distribution);
var sgVpcOrigins = SecurityGroup.FromSecurityGroupId(this, "VpcOriginsSecurityGroup", getSg.GetResponseField("SecurityGroups.0.GroupId"));
// Allow connections from the security group
alb.Connections.AllowFrom(sgVpcOrigins, Port.HTTP);
go
import "github.com/aws/aws-cdk-go/awscdk"

var vpc Vpc
var distribution Distribution
var alb ApplicationLoadBalancer


// Call ec2:DescribeSecurityGroups API to retrieve the VPC origins security group.
getSg := cr.NewAwsCustomResource(this, jsii.String("GetSecurityGroup"), &AwsCustomResourceProps{
	OnCreate: &AwsSdkCall{
		Service: jsii.String("ec2"),
		Action: jsii.String("describeSecurityGroups"),
		Parameters: map[string][]map[string]interface{}{
			"Filters": []map[string]interface{}{
				map[string]interface{}{
					"Name": jsii.String("vpc-id"),
					"Values": []*string{
						vpc.vpcId,
					},
				},
				map[string]interface{}{
					"Name": jsii.String("group-name"),
					"Values": []*string{
						jsii.String("CloudFront-VPCOrigins-Service-SG"),
					},
				},
			},
		},
		PhysicalResourceId: cr.PhysicalResourceId_Of(jsii.String("CloudFront-VPCOrigins-Service-SG")),
	},
	Policy: cr.AwsCustomResourcePolicy_FromSdkCalls(&SdkCallsPolicyOptions{
		Resources: []*string{
			jsii.String("*"),
		},
	}),
})
// The security group may be available after the distributon is deployed
getSg.Node.AddDependency(distribution)
sgVpcOrigins := ec2.SecurityGroup_FromSecurityGroupId(this, jsii.String("VpcOriginsSecurityGroup"), getSg.GetResponseField(jsii.String("SecurityGroups.0.GroupId")))
// Allow connections from the security group
alb.Connections.AllowFrom(sgVpcOrigins, ec2.Port_HTTP())

Failover Origins (Origin Groups) ​

You can set up CloudFront with origin failover for scenarios that require high availability. To get started, you create an origin group with two origins: a primary and a secondary. If the primary origin is unavailable, or returns specific HTTP response status codes that indicate a failure, CloudFront automatically switches to the secondary origin. You achieve that behavior in the CDK using the OriginGroup class:

ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: new origins.OriginGroup({
      primaryOrigin: origins.S3BucketOrigin.withOriginAccessControl(myBucket),
      fallbackOrigin: new origins.HttpOrigin('www.example.com'),
      // optional, defaults to: 500, 502, 503 and 504
      fallbackStatusCodes: [404],
    }),
  },
});
python
my_bucket = s3.Bucket(self, "myBucket")
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.OriginGroup(
            primary_origin=origins.S3BucketOrigin.with_origin_access_control(my_bucket),
            fallback_origin=origins.HttpOrigin("www.example.com"),
            # optional, defaults to: 500, 502, 503 and 504
            fallback_status_codes=[404]
        )
    )
)
java
Bucket myBucket = new Bucket(this, "myBucket");
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(OriginGroup.Builder.create()
                        .primaryOrigin(S3BucketOrigin.withOriginAccessControl(myBucket))
                        .fallbackOrigin(new HttpOrigin("www.example.com"))
                        // optional, defaults to: 500, 502, 503 and 504
                        .fallbackStatusCodes(List.of(404))
                        .build())
                .build())
        .build();
csharp
var myBucket = new Bucket(this, "myBucket");
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new OriginGroup(new OriginGroupProps {
            PrimaryOrigin = S3BucketOrigin.WithOriginAccessControl(myBucket),
            FallbackOrigin = new HttpOrigin("www.example.com"),
            // optional, defaults to: 500, 502, 503 and 504
            FallbackStatusCodes = new [] { 404 }
        })
    }
});
go
myBucket := s3.NewBucket(this, jsii.String("myBucket"))
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewOriginGroup(&OriginGroupProps{
			PrimaryOrigin: origins.S3BucketOrigin_WithOriginAccessControl(myBucket),
			FallbackOrigin: origins.NewHttpOrigin(jsii.String("www.example.com")),
			// optional, defaults to: 500, 502, 503 and 504
			FallbackStatusCodes: []*f64{
				jsii.Number(404),
			},
		}),
	},
})

Selection Criteria: Media Quality Based with AWS Elemental MediaPackageV2 ​

You can setup your origin group to be configured for media quality based failover with your AWS Elemental MediaPackageV2 endpoints. You can achieve this behavior in the CDK, again using the OriginGroup class:

ts
new cloudfront.Distribution(this, 'myDist', {
  defaultBehavior: {
    origin: new origins.OriginGroup({
      primaryOrigin: new origins.HttpOrigin("<AWS Elemental MediaPackageV2 origin 1>"),
      fallbackOrigin: new origins.HttpOrigin("<AWS Elemental MediaPackageV2 origin 2>"),
      fallbackStatusCodes: [404],
      selectionCriteria: cloudfront.OriginSelectionCriteria.MEDIA_QUALITY_BASED,
    }),
  },
});
python
cloudfront.Distribution(self, "myDist",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.OriginGroup(
            primary_origin=origins.HttpOrigin("<AWS Elemental MediaPackageV2 origin 1>"),
            fallback_origin=origins.HttpOrigin("<AWS Elemental MediaPackageV2 origin 2>"),
            fallback_status_codes=[404],
            selection_criteria=cloudfront.OriginSelectionCriteria.MEDIA_QUALITY_BASED
        )
    )
)
java
Distribution.Builder.create(this, "myDist")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(OriginGroup.Builder.create()
                        .primaryOrigin(new HttpOrigin("<AWS Elemental MediaPackageV2 origin 1>"))
                        .fallbackOrigin(new HttpOrigin("<AWS Elemental MediaPackageV2 origin 2>"))
                        .fallbackStatusCodes(List.of(404))
                        .selectionCriteria(OriginSelectionCriteria.MEDIA_QUALITY_BASED)
                        .build())
                .build())
        .build();
csharp
new Distribution(this, "myDist", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new OriginGroup(new OriginGroupProps {
            PrimaryOrigin = new HttpOrigin("<AWS Elemental MediaPackageV2 origin 1>"),
            FallbackOrigin = new HttpOrigin("<AWS Elemental MediaPackageV2 origin 2>"),
            FallbackStatusCodes = new [] { 404 },
            SelectionCriteria = OriginSelectionCriteria.MEDIA_QUALITY_BASED
        })
    }
});
go
cloudfront.NewDistribution(this, jsii.String("myDist"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewOriginGroup(&OriginGroupProps{
			PrimaryOrigin: origins.NewHttpOrigin(jsii.String("<AWS Elemental MediaPackageV2 origin 1>")),
			FallbackOrigin: origins.NewHttpOrigin(jsii.String("<AWS Elemental MediaPackageV2 origin 2>")),
			FallbackStatusCodes: []*f64{
				jsii.Number(404),
			},
			SelectionCriteria: cloudfront.OriginSelectionCriteria_MEDIA_QUALITY_BASED,
		}),
	},
})

From an API Gateway REST API ​

Origins can be created from an API Gateway REST API. It is recommended to use a regional API in this case. The origin path will automatically be set as the stage name.

ts
declare const api: apigateway.RestApi;
new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin: new origins.RestApiOrigin(api) },
});
python
# api: apigateway.RestApi

cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.RestApiOrigin(api))
)
java
RestApi api;

Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder().origin(new RestApiOrigin(api)).build())
        .build();
csharp
RestApi api;

new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new RestApiOrigin(api) }
});
go
var api RestApi

cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewRestApiOrigin(api),
	},
})

If you want to use a different origin path, you can specify it in the originPath property.

ts
declare const api: apigateway.RestApi;
new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin: new origins.RestApiOrigin(api, { originPath: '/custom-origin-path' }) },
});
python
# api: apigateway.RestApi

cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.RestApiOrigin(api, origin_path="/custom-origin-path"))
)
java
RestApi api;

Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder().origin(RestApiOrigin.Builder.create(api).originPath("/custom-origin-path").build()).build())
        .build();
csharp
RestApi api;

new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new RestApiOrigin(api, new RestApiOriginProps { OriginPath = "/custom-origin-path" }) }
});
go
var api RestApi

cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewRestApiOrigin(api, &RestApiOriginProps{
			OriginPath: jsii.String("/custom-origin-path"),
		}),
	},
})

From a Lambda Function URL ​

Lambda Function URLs enable direct invocation of Lambda functions via HTTP(S), without intermediaries. They can be set as CloudFront origins for streamlined function execution behind a CDN, leveraging caching and custom domains.

ts
import * as lambda from 'aws-cdk-lib/aws-lambda';

declare const fn: lambda.Function;
const fnUrl = fn.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE });

new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: { origin: new origins.FunctionUrlOrigin(fnUrl) },
});
python
import aws_cdk.aws_lambda as lambda_

# fn: lambda.Function

fn_url = fn.add_function_url(auth_type=lambda_.FunctionUrlAuthType.NONE)

cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(origin=origins.FunctionUrlOrigin(fn_url))
)
java
import software.amazon.awscdk.services.lambda.*;

Function fn;

FunctionUrl fnUrl = fn.addFunctionUrl(FunctionUrlOptions.builder().authType(FunctionUrlAuthType.NONE).build());

Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder().origin(new FunctionUrlOrigin(fnUrl)).build())
        .build();
csharp
using Amazon.CDK.AWS.Lambda;

Function fn;

var fnUrl = fn.AddFunctionUrl(new FunctionUrlOptions { AuthType = FunctionUrlAuthType.NONE });

new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions { Origin = new FunctionUrlOrigin(fnUrl) }
});
go
import lambda "github.com/aws/aws-cdk-go/awscdk"

var fn Function

fnUrl := fn.AddFunctionUrl(&FunctionUrlOptions{
	AuthType: lambda.FunctionUrlAuthType_NONE,
})

cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewFunctionUrlOrigin(fnUrl),
	},
})

You can also configure timeout settings for Lambda Function URL origins:

ts
import * as lambda from 'aws-cdk-lib/aws-lambda';

declare const fn: lambda.Function;
const fnUrl = fn.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE });

new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: {
    origin: new origins.FunctionUrlOrigin(fnUrl, {
      readTimeout: Duration.seconds(30),
      responseCompletionTimeout: Duration.seconds(90),
      keepaliveTimeout: Duration.seconds(45),
    }),
  },
});
python
import aws_cdk.aws_lambda as lambda_

# fn: lambda.Function

fn_url = fn.add_function_url(auth_type=lambda_.FunctionUrlAuthType.NONE)

cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.FunctionUrlOrigin(fn_url,
            read_timeout=Duration.seconds(30),
            response_completion_timeout=Duration.seconds(90),
            keepalive_timeout=Duration.seconds(45)
        )
    )
)
java
import software.amazon.awscdk.services.lambda.*;

Function fn;

FunctionUrl fnUrl = fn.addFunctionUrl(FunctionUrlOptions.builder().authType(FunctionUrlAuthType.NONE).build());

Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(FunctionUrlOrigin.Builder.create(fnUrl)
                        .readTimeout(Duration.seconds(30))
                        .responseCompletionTimeout(Duration.seconds(90))
                        .keepaliveTimeout(Duration.seconds(45))
                        .build())
                .build())
        .build();
csharp
using Amazon.CDK.AWS.Lambda;

Function fn;

var fnUrl = fn.AddFunctionUrl(new FunctionUrlOptions { AuthType = FunctionUrlAuthType.NONE });

new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new FunctionUrlOrigin(fnUrl, new FunctionUrlOriginProps {
            ReadTimeout = Duration.Seconds(30),
            ResponseCompletionTimeout = Duration.Seconds(90),
            KeepaliveTimeout = Duration.Seconds(45)
        })
    }
});
go
import lambda "github.com/aws/aws-cdk-go/awscdk"

var fn Function

fnUrl := fn.AddFunctionUrl(&FunctionUrlOptions{
	AuthType: lambda.FunctionUrlAuthType_NONE,
})

cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewFunctionUrlOrigin(fnUrl, &FunctionUrlOriginProps{
			ReadTimeout: awscdk.Duration_Seconds(jsii.Number(30)),
			ResponseCompletionTimeout: awscdk.Duration_*Seconds(jsii.Number(90)),
			KeepaliveTimeout: awscdk.Duration_*Seconds(jsii.Number(45)),
		}),
	},
})

Configuring IP Address Type ​

You can specify which IP protocol CloudFront uses when connecting to your Lambda Function URL origin. By default, CloudFront uses IPv4 only.

ts
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { OriginIpAddressType } from 'aws-cdk-lib/aws-cloudfront';

declare const fn: lambda.Function;
const fnUrl = fn.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE });

// Uses default IPv4 only
new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: {
    origin: new origins.FunctionUrlOrigin(fnUrl)
  },
});

// Explicitly specify IP address type
new cloudfront.Distribution(this, 'Distribution', {
  defaultBehavior: {
    origin: new origins.FunctionUrlOrigin(fnUrl, {
      ipAddressType: OriginIpAddressType.DUALSTACK, // Use both IPv4 and IPv6
    })
  },
});
python
import aws_cdk.aws_lambda as lambda_
from aws_cdk.aws_cloudfront import OriginIpAddressType

# fn: lambda.Function

fn_url = fn.add_function_url(auth_type=lambda_.FunctionUrlAuthType.NONE)

# Uses default IPv4 only
cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.FunctionUrlOrigin(fn_url)
    )
)

# Explicitly specify IP address type
cloudfront.Distribution(self, "Distribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.FunctionUrlOrigin(fn_url,
            ip_address_type=OriginIpAddressType.DUALSTACK
        )
    )
)
java
import software.amazon.awscdk.services.lambda.*;
import software.amazon.awscdk.services.cloudfront.OriginIpAddressType;

Function fn;

FunctionUrl fnUrl = fn.addFunctionUrl(FunctionUrlOptions.builder().authType(FunctionUrlAuthType.NONE).build());

// Uses default IPv4 only
// Uses default IPv4 only
Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(new FunctionUrlOrigin(fnUrl))
                .build())
        .build();

// Explicitly specify IP address type
// Explicitly specify IP address type
Distribution.Builder.create(this, "Distribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(FunctionUrlOrigin.Builder.create(fnUrl)
                        .ipAddressType(OriginIpAddressType.DUALSTACK)
                        .build())
                .build())
        .build();
csharp
using Amazon.CDK.AWS.Lambda;
using Amazon.CDK.AWS.CloudFront;

Function fn;

var fnUrl = fn.AddFunctionUrl(new FunctionUrlOptions { AuthType = FunctionUrlAuthType.NONE });

// Uses default IPv4 only
// Uses default IPv4 only
new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new FunctionUrlOrigin(fnUrl)
    }
});

// Explicitly specify IP address type
// Explicitly specify IP address type
new Distribution(this, "Distribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new FunctionUrlOrigin(fnUrl, new FunctionUrlOriginProps {
            IpAddressType = OriginIpAddressType.DUALSTACK
        })
    }
});
go
import lambda "github.com/aws/aws-cdk-go/awscdk"
import "github.com/aws/aws-cdk-go/awscdk"

var fn Function

fnUrl := fn.AddFunctionUrl(&FunctionUrlOptions{
	AuthType: lambda.FunctionUrlAuthType_NONE,
})

// Uses default IPv4 only
// Uses default IPv4 only
cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewFunctionUrlOrigin(fnUrl),
	},
})

// Explicitly specify IP address type
// Explicitly specify IP address type
cloudfront.NewDistribution(this, jsii.String("Distribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewFunctionUrlOrigin(fnUrl, &FunctionUrlOriginProps{
			IpAddressType: awscdk.OriginIpAddressType_DUALSTACK,
		}),
	},
})

Supported values for ipAddressType:

  • OriginIpAddressType.IPV4 - CloudFront uses IPv4 only to connect to the origin (default)
  • OriginIpAddressType.IPV6 - CloudFront uses IPv6 only to connect to the origin
  • OriginIpAddressType.DUALSTACK - CloudFront uses both IPv4 and IPv6 to connect to the origin

Lambda Function URL with Origin Access Control (OAC) ​

You can configure the Lambda Function URL with Origin Access Control (OAC) for enhanced security. When using OAC with Signing SIGV4_ALWAYS, it is recommended to set the Lambda Function URL authType to AWS_IAM to ensure proper authorization.

ts
import * as lambda from 'aws-cdk-lib/aws-lambda';
declare const fn: lambda.Function;

const fnUrl = fn.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.AWS_IAM,
});

new cloudfront.Distribution(this, 'MyDistribution', {
  defaultBehavior: {
    origin: origins.FunctionUrlOrigin.withOriginAccessControl(fnUrl),
  },
});
python
import aws_cdk.aws_lambda as lambda_
# fn: lambda.Function


fn_url = fn.add_function_url(
    auth_type=lambda_.FunctionUrlAuthType.AWS_IAM
)

cloudfront.Distribution(self, "MyDistribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.FunctionUrlOrigin.with_origin_access_control(fn_url)
    )
)
java
import software.amazon.awscdk.services.lambda.*;
Function fn;


FunctionUrl fnUrl = fn.addFunctionUrl(FunctionUrlOptions.builder()
        .authType(FunctionUrlAuthType.AWS_IAM)
        .build());

Distribution.Builder.create(this, "MyDistribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(FunctionUrlOrigin.withOriginAccessControl(fnUrl))
                .build())
        .build();
csharp
using Amazon.CDK.AWS.Lambda;
Function fn;


var fnUrl = fn.AddFunctionUrl(new FunctionUrlOptions {
    AuthType = FunctionUrlAuthType.AWS_IAM
});

new Distribution(this, "MyDistribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = FunctionUrlOrigin.WithOriginAccessControl(fnUrl)
    }
});
go
import lambda "github.com/aws/aws-cdk-go/awscdk"
var fn Function


fnUrl := fn.AddFunctionUrl(&FunctionUrlOptions{
	AuthType: lambda.FunctionUrlAuthType_AWS_IAM,
})

cloudfront.NewDistribution(this, jsii.String("MyDistribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.FunctionUrlOrigin_WithOriginAccessControl(fnUrl),
	},
})

If you want to explicitly add OAC for more customized access control, you can use the originAccessControl option as shown below.

ts
import * as lambda from 'aws-cdk-lib/aws-lambda';
declare const fn: lambda.Function;

const fnUrl = fn.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.AWS_IAM,
});

// Define a custom OAC
const oac = new cloudfront.FunctionUrlOriginAccessControl(this, 'MyOAC', {
  originAccessControlName: 'CustomLambdaOAC',
  signing: cloudfront.Signing.SIGV4_ALWAYS,
});

// Set up Lambda Function URL with OAC in CloudFront Distribution
new cloudfront.Distribution(this, 'MyDistribution', {
  defaultBehavior: {
    origin: origins.FunctionUrlOrigin.withOriginAccessControl(fnUrl, {
      originAccessControl: oac,
    }),
  },
});
python
import aws_cdk.aws_lambda as lambda_
# fn: lambda.Function


fn_url = fn.add_function_url(
    auth_type=lambda_.FunctionUrlAuthType.AWS_IAM
)

# Define a custom OAC
oac = cloudfront.FunctionUrlOriginAccessControl(self, "MyOAC",
    origin_access_control_name="CustomLambdaOAC",
    signing=cloudfront.Signing.SIGV4_ALWAYS
)

# Set up Lambda Function URL with OAC in CloudFront Distribution
cloudfront.Distribution(self, "MyDistribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.FunctionUrlOrigin.with_origin_access_control(fn_url,
            origin_access_control=oac
        )
    )
)
java
import software.amazon.awscdk.services.lambda.*;
Function fn;


FunctionUrl fnUrl = fn.addFunctionUrl(FunctionUrlOptions.builder()
        .authType(FunctionUrlAuthType.AWS_IAM)
        .build());

// Define a custom OAC
FunctionUrlOriginAccessControl oac = FunctionUrlOriginAccessControl.Builder.create(this, "MyOAC")
        .originAccessControlName("CustomLambdaOAC")
        .signing(Signing.SIGV4_ALWAYS)
        .build();

// Set up Lambda Function URL with OAC in CloudFront Distribution
// Set up Lambda Function URL with OAC in CloudFront Distribution
Distribution.Builder.create(this, "MyDistribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(FunctionUrlOrigin.withOriginAccessControl(fnUrl, FunctionUrlOriginWithOACProps.builder()
                        .originAccessControl(oac)
                        .build()))
                .build())
        .build();
csharp
using Amazon.CDK.AWS.Lambda;
Function fn;


var fnUrl = fn.AddFunctionUrl(new FunctionUrlOptions {
    AuthType = FunctionUrlAuthType.AWS_IAM
});

// Define a custom OAC
var oac = new FunctionUrlOriginAccessControl(this, "MyOAC", new FunctionUrlOriginAccessControlProps {
    OriginAccessControlName = "CustomLambdaOAC",
    Signing = Signing.SIGV4_ALWAYS
});

// Set up Lambda Function URL with OAC in CloudFront Distribution
// Set up Lambda Function URL with OAC in CloudFront Distribution
new Distribution(this, "MyDistribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = FunctionUrlOrigin.WithOriginAccessControl(fnUrl, new FunctionUrlOriginWithOACProps {
            OriginAccessControl = oac
        })
    }
});
go
import lambda "github.com/aws/aws-cdk-go/awscdk"
var fn Function


fnUrl := fn.AddFunctionUrl(&FunctionUrlOptions{
	AuthType: lambda.FunctionUrlAuthType_AWS_IAM,
})

// Define a custom OAC
oac := cloudfront.NewFunctionUrlOriginAccessControl(this, jsii.String("MyOAC"), &FunctionUrlOriginAccessControlProps{
	OriginAccessControlName: jsii.String("CustomLambdaOAC"),
	Signing: cloudfront.Signing_SIGV4_ALWAYS(),
})

// Set up Lambda Function URL with OAC in CloudFront Distribution
// Set up Lambda Function URL with OAC in CloudFront Distribution
cloudfront.NewDistribution(this, jsii.String("MyDistribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.FunctionUrlOrigin_WithOriginAccessControl(fnUrl, &FunctionUrlOriginWithOACProps{
			OriginAccessControl: oac,
		}),
	},
})

Function URL permissions ​

Lambda requires both lambda:InvokeFunctionUrl and lambda:InvokeFunction for CloudFront to call a newly created function URL through OAC. FunctionUrlOrigin.withOriginAccessControl adds both permissions.

From a Lambda Web Function ​

An AWS Lambda Web Function endpoint with the IamAuth authentication type can be a CloudFront origin protected by origin access control. Web Function origins support only the always-amz-auth signing behavior, which signs every origin request and passes the viewer's Authorization header to the function unchanged. For a regional endpoint, use Signing.SIGV4_ALWAYS_AMZ_AUTH:

ts
const oac = new cloudfront.FunctionUrlOriginAccessControl(this, 'WebFunctionOAC', {
  signing: cloudfront.Signing.SIGV4_ALWAYS_AMZ_AUTH,
});

new cloudfront.Distribution(this, 'MyDistribution', {
  defaultBehavior: {
    origin: new origins.HttpOrigin('bcdfghjklmnpqrst.b7t.lambda-web.us-east-1.on.aws', {
      originAccessControlId: oac.originAccessControlId,
    }),
    originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
  },
});
python
oac = cloudfront.FunctionUrlOriginAccessControl(self, "WebFunctionOAC",
    signing=cloudfront.Signing.SIGV4_ALWAYS_AMZ_AUTH
)

cloudfront.Distribution(self, "MyDistribution",
    default_behavior=cloudfront.BehaviorOptions(
        origin=origins.HttpOrigin("bcdfghjklmnpqrst.b7t.lambda-web.us-east-1.on.aws",
            origin_access_control_id=oac.origin_access_control_id
        ),
        origin_request_policy=cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
    )
)
java
FunctionUrlOriginAccessControl oac = FunctionUrlOriginAccessControl.Builder.create(this, "WebFunctionOAC")
        .signing(Signing.SIGV4_ALWAYS_AMZ_AUTH)
        .build();

Distribution.Builder.create(this, "MyDistribution")
        .defaultBehavior(BehaviorOptions.builder()
                .origin(HttpOrigin.Builder.create("bcdfghjklmnpqrst.b7t.lambda-web.us-east-1.on.aws")
                        .originAccessControlId(oac.getOriginAccessControlId())
                        .build())
                .originRequestPolicy(OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER)
                .build())
        .build();
csharp
var oac = new FunctionUrlOriginAccessControl(this, "WebFunctionOAC", new FunctionUrlOriginAccessControlProps {
    Signing = Signing.SIGV4_ALWAYS_AMZ_AUTH
});

new Distribution(this, "MyDistribution", new DistributionProps {
    DefaultBehavior = new BehaviorOptions {
        Origin = new HttpOrigin("bcdfghjklmnpqrst.b7t.lambda-web.us-east-1.on.aws", new HttpOriginProps {
            OriginAccessControlId = oac.OriginAccessControlId
        }),
        OriginRequestPolicy = OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
    }
});
go
oac := cloudfront.NewFunctionUrlOriginAccessControl(this, jsii.String("WebFunctionOAC"), &FunctionUrlOriginAccessControlProps{
	Signing: cloudfront.Signing_SIGV4_ALWAYS_AMZ_AUTH(),
})

cloudfront.NewDistribution(this, jsii.String("MyDistribution"), &DistributionProps{
	DefaultBehavior: &BehaviorOptions{
		Origin: origins.NewHttpOrigin(jsii.String("bcdfghjklmnpqrst.b7t.lambda-web.us-east-1.on.aws"), &HttpOriginProps{
			OriginAccessControlId: oac.OriginAccessControlId,
		}),
		OriginRequestPolicy: cloudfront.OriginRequestPolicy_ALL_VIEWER_EXCEPT_HOST_HEADER(),
	},
})

Web Function endpoints return HTTP 404 if the Host header isn't the endpoint domain name, so don't forward the viewer's Host header. You must also attach a resource-based policy to the Web Function that allows cloudfront.amazonaws.com to call lambda:InvokeWebFunctionEndpoint. See Restrict access to an AWS Lambda Web Function origin.

Types ​

NameKindSummary
FunctionUrlOriginClassAn Origin for a Lambda Function URL.
FunctionUrlOriginBasePropsStructProperties for configuring a origin using a standard Lambda Functions URLs.
FunctionUrlOriginPropsStructProperties for a Lambda Function URL Origin.
FunctionUrlOriginWithOACPropsStructProperties for configuring a Lambda Functions URLs with OAC.
HttpOriginClassAn Origin for an HTTP server or S3 bucket configured for website hosting.
HttpOriginPropsStructProperties for an Origin backed by an S3 website-configured bucket, load balancer, or custom HTTP server.
LoadBalancerV2OriginClassAn Origin for a v2 load balancer.
LoadBalancerV2OriginPropsStructProperties for an Origin backed by a v2 load balancer.
OriginGroupClassAn Origin that represents a group.
OriginGroupPropsStructConstruction properties for OriginGroup.
RestApiOriginClassAn Origin for an API Gateway REST API.
RestApiOriginPropsStructProperties for an Origin for an API Gateway REST API.
S3BucketOriginClassA S3 Bucket Origin.
S3BucketOriginBasePropsStructProperties for configuring a origin using a standard S3 bucket.
S3BucketOriginWithOACPropsStructProperties for configuring a S3 origin with OAC.
S3BucketOriginWithOAIPropsStructProperties for configuring a S3 origin with OAI.
S3OriginClassAn Origin that is backed by an S3 bucket.
S3OriginPropsStructProperties to use to customize an S3 Origin.
S3StaticWebsiteOriginClassAn Origin for a S3 bucket configured as a website endpoint.
S3StaticWebsiteOriginPropsStructProperties for configuring a origin using a S3 bucket configured as a website endpoint.
VpcOriginClassRepresents a distribution's VPC origin.
VpcOriginPropsStructProperties to define a VPC origin.
VpcOriginWithEndpointPropsStructProperties to define a VPC origin with endpoint.