From 4111187434dc60bcc8e4142b9a4c5d6867b6fc71 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Thu, 16 May 2024 14:50:34 -0700 Subject: [PATCH 1/7] wip --- text/0491-cloudfront-oac-l2.md | 183 +++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 text/0491-cloudfront-oac-l2.md diff --git a/text/0491-cloudfront-oac-l2.md b/text/0491-cloudfront-oac-l2.md new file mode 100644 index 000000000..c6ec3c5f0 --- /dev/null +++ b/text/0491-cloudfront-oac-l2.md @@ -0,0 +1,183 @@ +# CloudFront Origin Access Control L2 + +* **Original Author(s)**: @gracelu0 +* **Tracking Issue**: [#491](https://github.com/aws/aws-cdk-rfcs/issues/491) +* **API Bar Raiser**: @colifran + +[CloudFront Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) (OAC) is the recommended way to send authenticated requests to an Amazon S3 origin using IAM service principals. It offers better security, supports server-side encryption with AWS KMS, and supports all Amazon S3 buckets in all AWS regions. + +Currently the `S3Origin` construct automatically creates an Origin Access Identity (OAI) to restrict access to an S3 Origin. However, using OAI is now considered legacy and no longer recommended. CDK users who want to use OAC currently have to use the L1 construct `CfnOriginAccessControl`. They need to use escape hatches to attach the OAC to their CloudFront distribution and remove the OAI that is automatically configured. With a CloudFront OAC L2 construct, users will be able to set up their CloudFront origins using OAC instead of OAI in a much cleaner, simpler way. + +## Working Backwards + +### CHANGELOG + +`feat(cloudfront): origin access control L2 construct` + +### README + +# Amazon CloudFront Construct Library +Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best possible performance. + +## Creating a Distribution + +CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your content. Origins can be created from S3 buckets or a custom origin (HTTP server). Constructs to define origins are in the aws-cdk-lib/aws-cloudfront-origins module. + +Each distribution has a default behavior which applies to all requests to that distribution, and routes requests to a primary origin. Additional behaviors may be specified for an origin with a given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to use HTTPS, and what query strings or cookies to forward to your origin, among other settings. + +#### From an S3 Bucket + +An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. + +```ts +// Creates a distribution from an S3 bucket. +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.S3Origin(myBucket) }, +}); +``` + +The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is +treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and +CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an origin access control and grant it access to the +underlying bucket. This 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. + +## Restricting access to an S3 origin + +## Migrating from OAI to OAC + +# 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 added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.S3Origin(myBucket) }, +}); +``` + +The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is +treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and +CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an **origin access control** and grant it access to the +underlying bucket. This 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. Alternatively, a custom origin access control can be passed to the S3 origin in the properties. + + + + + + +--- + +Ticking the box below indicates that the public API of this RFC has been +signed-off by the API bar raiser (the `status/api-approved` label was applied to the +RFC pull request): + +``` +[ ] Signed-off by API Bar Raiser @xxxxx +``` + +## Public FAQ + +> This section should include answers to questions readers will likely ask about +> this release. Similar to the "working backwards", this section should be +> written in a language as if the feature is now released. +> +> The template includes a some common questions, feel free to add any questions +> that might be relevant to this feature or omit questions that you feel are not +> applicable. + +### What are we launching today? + +> What exactly are we launching? Is this a new feature in an existing module? A +> new module? A whole framework? A change in the CLI? + +We are launching a new L2 construct `OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`). We are also launching some modifications to the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module. + +### Why should I use this feature? + +Use Origin Access Control to restrict access to your AWS origin so that it's not publicly accessible. This ensures users only access the content in your AWS origin through your specified CloudFront distribution. OAC is recommended instead of OAI for its latest security best practices and support in new AWS regions. + +## Internal FAQ + +> The goal of this section is to help decide if this RFC should be implemented. +> It should include answers to questions that the team is likely ask. Contrary +> to the rest of the RFC, answers should be written "from the present" and +> likely discuss design approach, implementation plans, alternative considered +> and other considerations that will help decide if this RFC should be +> implemented. + +### Why are we doing this? + +This feature has been highly requested by the community, since Origin Access Control has been available since August 2022. Although the L1 construct `CfnOriginAccessControl` exists, users need to remove the OAI automatically configured by the existing `S3Origin` construct which is a subpar user experience. + +### Why should we _not_ do this? + +> Is there a way to address this use case with the current product? What are the +> downsides of implementing this feature? + +Users have already found workarounds using the L1 construct. + +### What is the technical solution (design) of this feature? + +> Briefly describe the high-level design approach for implementing this feature. +> +> As appropriate, you can add an appendix with a more detailed design document. +> +> This is a good place to reference a prototype or proof of concept, which is +> highly recommended for most RFCs. + +See prototype branch here + + +### Is this a breaking change? + +No, this is not a breaking change. This is a new feature and the existing setup using OAI will still be supported. + +### What alternative solutions did you consider? + +> Briefly describe alternative approaches that you considered. If there are +> hairy details, include them in an appendix. + + + +### What are the drawbacks of this solution? + +> Describe any problems/risks that can be introduced if we implement this RFC. + +Existing customers who wish to migrate from OAI to OAC will need to update their CDK code. Customers who are using the L1 construct for OAC will also need to make changes to their CDK code to use the new L2 construct. + +### What is the high-level project plan? + +- [ ] Create prototype for design +- [ ] Gather feedback on the RFC +- [ ] Get bar raiser to sign off on RFC +- [ ] Implement the construct in a separate repository +- [ ] Make pull request to aws-cdk repository +- [ ] Iterate and respond to PR feedback +- [ ] Merge new construct and related changes + +### Are there any open issues that need to be addressed later? + +> Describe any major open issues that this RFC did not take into account. Once +> the RFC is approved, create GitHub issues for these issues and update this RFC +> of the project board with these issue IDs. + +Supporting Origin Access Control for Lambda Function Url origins. + +## Appendix + +Feel free to add any number of appendices as you see fit. Appendices are +expected to allow readers to dive deeper to certain sections if they like. For +example, you can include an appendix which describes the detailed design of an +algorithm and reference it from the FAQ. From f3ab7a4fc1e953e04a3b6658d959ca0c25502516 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Wed, 29 May 2024 14:10:49 -0700 Subject: [PATCH 2/7] Fill in sections --- text/0491-cloudfront-oac-l2.md | 183 ----------- text/0617-cloudfront-oac-l2.md | 581 +++++++++++++++++++++++++++++++++ 2 files changed, 581 insertions(+), 183 deletions(-) delete mode 100644 text/0491-cloudfront-oac-l2.md create mode 100644 text/0617-cloudfront-oac-l2.md diff --git a/text/0491-cloudfront-oac-l2.md b/text/0491-cloudfront-oac-l2.md deleted file mode 100644 index c6ec3c5f0..000000000 --- a/text/0491-cloudfront-oac-l2.md +++ /dev/null @@ -1,183 +0,0 @@ -# CloudFront Origin Access Control L2 - -* **Original Author(s)**: @gracelu0 -* **Tracking Issue**: [#491](https://github.com/aws/aws-cdk-rfcs/issues/491) -* **API Bar Raiser**: @colifran - -[CloudFront Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) (OAC) is the recommended way to send authenticated requests to an Amazon S3 origin using IAM service principals. It offers better security, supports server-side encryption with AWS KMS, and supports all Amazon S3 buckets in all AWS regions. - -Currently the `S3Origin` construct automatically creates an Origin Access Identity (OAI) to restrict access to an S3 Origin. However, using OAI is now considered legacy and no longer recommended. CDK users who want to use OAC currently have to use the L1 construct `CfnOriginAccessControl`. They need to use escape hatches to attach the OAC to their CloudFront distribution and remove the OAI that is automatically configured. With a CloudFront OAC L2 construct, users will be able to set up their CloudFront origins using OAC instead of OAI in a much cleaner, simpler way. - -## Working Backwards - -### CHANGELOG - -`feat(cloudfront): origin access control L2 construct` - -### README - -# Amazon CloudFront Construct Library -Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best possible performance. - -## Creating a Distribution - -CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your content. Origins can be created from S3 buckets or a custom origin (HTTP server). Constructs to define origins are in the aws-cdk-lib/aws-cloudfront-origins module. - -Each distribution has a default behavior which applies to all requests to that distribution, and routes requests to a primary origin. Additional behaviors may be specified for an origin with a given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to use HTTPS, and what query strings or cookies to forward to your origin, among other settings. - -#### From an S3 Bucket - -An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error -documents. - -```ts -// Creates a distribution from an S3 bucket. -const myBucket = new s3.Bucket(this, 'myBucket'); -new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { origin: new origins.S3Origin(myBucket) }, -}); -``` - -The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is -treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and -CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an origin access control and grant it access to the -underlying bucket. This 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. - -## Restricting access to an S3 origin - -## Migrating from OAI to OAC - -# 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 added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error -documents. - -```ts -const myBucket = new s3.Bucket(this, 'myBucket'); -new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { origin: new origins.S3Origin(myBucket) }, -}); -``` - -The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is -treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and -CloudFront's redirect and error handling will be used. In the latter case, the Origin will create an **origin access control** and grant it access to the -underlying bucket. This 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. Alternatively, a custom origin access control can be passed to the S3 origin in the properties. - - - - - - ---- - -Ticking the box below indicates that the public API of this RFC has been -signed-off by the API bar raiser (the `status/api-approved` label was applied to the -RFC pull request): - -``` -[ ] Signed-off by API Bar Raiser @xxxxx -``` - -## Public FAQ - -> This section should include answers to questions readers will likely ask about -> this release. Similar to the "working backwards", this section should be -> written in a language as if the feature is now released. -> -> The template includes a some common questions, feel free to add any questions -> that might be relevant to this feature or omit questions that you feel are not -> applicable. - -### What are we launching today? - -> What exactly are we launching? Is this a new feature in an existing module? A -> new module? A whole framework? A change in the CLI? - -We are launching a new L2 construct `OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`). We are also launching some modifications to the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module. - -### Why should I use this feature? - -Use Origin Access Control to restrict access to your AWS origin so that it's not publicly accessible. This ensures users only access the content in your AWS origin through your specified CloudFront distribution. OAC is recommended instead of OAI for its latest security best practices and support in new AWS regions. - -## Internal FAQ - -> The goal of this section is to help decide if this RFC should be implemented. -> It should include answers to questions that the team is likely ask. Contrary -> to the rest of the RFC, answers should be written "from the present" and -> likely discuss design approach, implementation plans, alternative considered -> and other considerations that will help decide if this RFC should be -> implemented. - -### Why are we doing this? - -This feature has been highly requested by the community, since Origin Access Control has been available since August 2022. Although the L1 construct `CfnOriginAccessControl` exists, users need to remove the OAI automatically configured by the existing `S3Origin` construct which is a subpar user experience. - -### Why should we _not_ do this? - -> Is there a way to address this use case with the current product? What are the -> downsides of implementing this feature? - -Users have already found workarounds using the L1 construct. - -### What is the technical solution (design) of this feature? - -> Briefly describe the high-level design approach for implementing this feature. -> -> As appropriate, you can add an appendix with a more detailed design document. -> -> This is a good place to reference a prototype or proof of concept, which is -> highly recommended for most RFCs. - -See prototype branch here - - -### Is this a breaking change? - -No, this is not a breaking change. This is a new feature and the existing setup using OAI will still be supported. - -### What alternative solutions did you consider? - -> Briefly describe alternative approaches that you considered. If there are -> hairy details, include them in an appendix. - - - -### What are the drawbacks of this solution? - -> Describe any problems/risks that can be introduced if we implement this RFC. - -Existing customers who wish to migrate from OAI to OAC will need to update their CDK code. Customers who are using the L1 construct for OAC will also need to make changes to their CDK code to use the new L2 construct. - -### What is the high-level project plan? - -- [ ] Create prototype for design -- [ ] Gather feedback on the RFC -- [ ] Get bar raiser to sign off on RFC -- [ ] Implement the construct in a separate repository -- [ ] Make pull request to aws-cdk repository -- [ ] Iterate and respond to PR feedback -- [ ] Merge new construct and related changes - -### Are there any open issues that need to be addressed later? - -> Describe any major open issues that this RFC did not take into account. Once -> the RFC is approved, create GitHub issues for these issues and update this RFC -> of the project board with these issue IDs. - -Supporting Origin Access Control for Lambda Function Url origins. - -## Appendix - -Feel free to add any number of appendices as you see fit. Appendices are -expected to allow readers to dive deeper to certain sections if they like. For -example, you can include an appendix which describes the detailed design of an -algorithm and reference it from the FAQ. diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md new file mode 100644 index 000000000..9950a89b5 --- /dev/null +++ b/text/0617-cloudfront-oac-l2.md @@ -0,0 +1,581 @@ +# CloudFront Origin Access Control L2 + +* **Original Author(s)**: @gracelu0 +* **Tracking Issue**: [#617](https://github.com/aws/aws-cdk-rfcs/issues/617) +* **API Bar Raiser**: @colifran + +[CloudFront Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) (OAC) is the recommended way to send authenticated requests to an Amazon S3 origin using IAM service principals. It offers better security, supports server-side encryption with AWS KMS, and supports all Amazon S3 buckets in all AWS regions. + +Currently the `S3Origin` construct automatically creates an Origin Access Identity (OAI) to restrict access to an S3 Origin. However, using OAI is now considered [legacy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) and no longer recommended. CDK users who want to use OAC currently have to use the L1 construct `CfnOriginAccessControl`. They need to use escape hatches to attach the OAC to their CloudFront distribution and remove the OAI that is automatically configured. With a CloudFront OAC L2 construct, users will be able to easily set up their CloudFront origins using OAC instead of OAI. + +## Working Backwards + +### CHANGELOG + +`feat(cloudfront): origin access control L2 construct` + +### README + +# Amazon CloudFront Construct Library +Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best possible performance. + +## Creating a Distribution + +CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your content. Origins can be created from S3 buckets or a custom origin (HTTP server). Constructs to define origins are in the `aws-cdk-lib/aws-cloudfront-origins` module. + +Each distribution has a default behavior which applies to all requests to that distribution, and routes requests to a primary origin. Additional behaviors may be specified for an origin with a given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to use HTTPS, and what query strings or cookies to forward to your origin, among other settings. + +#### From an S3 Bucket + +An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. + +```ts +// Creates a distribution from an S3 bucket. +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.S3Origin(myBucket) }, +}); +``` + +The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is +treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and +CloudFront's redirect and error handling will be used. + +## Restricting access to an S3 origin + +CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC is the recommended option and OAI is considered legacy (see [Restricting access to an Amazon S3 Origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). These 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. + +> Note: OAC and OAI can only be used with an regular S3 bucket origin (not a bucket configured as a website endpoint). + +To setup origin access control for an S3 origin, you can create an `OriginAccessControl` +resource and pass it into the `originAccessControl` property of the origin: + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); +const oac = new cloudfront.OriginAccessControl(this, 'myS3OAC'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: new origins.S3Origin(myBucket, { + originAccessControl: oac + }) + }, +}); +``` +It is recommended to set the `@aws-cdk/aws-cloudfront:useOriginAccessControl` feature flag to `true`, so an OAC will be automatically created instead of an OAI when `S3Origin` is instantiated. If you don't set this feature flag, and OAI will be created and granted access to the underlying bucket. + + +## Migrating from OAI to OAC + +If you are currently using OAI for your S3 origin and wish to migrate to OAC, first set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` to `true` in `cdk.json`. With this feature flag set, when you create a new `S3Origin` an Origin Access Control will be used instead of Origin Access Identity. You can create and pass in an `OriginAccessControl` or one will be automatically created by default. + +For more information, see [Migrating from origin access identity (OAI) to origin access control (OAC)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac). + +### Using pre-existing S3 buckets + +If you are using an imported bucket for your S3 Origin and want to use OAC, first import the bucket using one of the import methods (`fromBucketName`, `fromBucketArn` or `fromBucketAttributes`). + +The `S3Origin` construct will update the S3 bucket policy to allow CloudFront read-only access. If your bucket previously used OAI, there will be an attempt to remove both the policy statement that allows access to the OAI and the origin access identity itself. + +```ts +const bucket = s3.Bucket.fromBucketArn(this, 'MyExistingBucket', + 'arn:aws:s3:::mybucketname' +); + +const oac = new cloudfront.OriginAccessControl(this, 'MyOAC', { + originAccessControlOriginType: cloudfront.OriginAccessControlOriginType.S3, +}); + +const distribution = new cloudfront.Distribution(this, 'MyDistribution', { + defaultBehavior: { + origin: new origins.S3Origin(bucket, { + originAccessControl: oac + }) + } +}); +``` + + +# CloudFront Origins for the CDK CloudFront Library + +## S3 Bucket + +An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error +documents. + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { origin: new origins.S3Origin(myBucket) }, +}); +``` + +The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is +treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and +CloudFront's redirect and error handling will be used. + +### Restricting access to an S3 Origin + +CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC is the recommended method and OAI is considered legacy (see [Restricting access to an Amazon Simple Storage Service origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). Following AWS best practices, it is recommended you set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` to `true` to use OAC by default when creating new origins. + +For an S3 bucket that is configured as a standard S3 bucket origin (not as a website endpoint), when the above feature flag is enabled the `S3Origin` construct will automatically create an OAC and grant it access to the underlying bucket. + +> [Note](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html): When you use OAC with S3 bucket origins you must set the bucket's object ownership to Bucket owner enforced, or Bucket owner preferred (only if you require ACLs). + +```ts +const myBucket = new s3.Bucket(this, 'myBucket', { + objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED, +}); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: new origins.S3Origin(myBucket) // Automatically creates an OAC + }, +}); +``` + +Alternatively, a custom origin access control can be passed to the S3 origin: + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); +const myOAC = new cloudfront.OriginAccessControl(this, 'myOAC', { + description: 'Origin access control for S3 origin', + originAccessControlOriginType: cloudfront.OriginAccessControlOriginType.S3, +}); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: new origins.S3Origin(myBucket, { + originAccessControl: myOAC + }), + }, +}); +``` + +If the feature flag is not enabled (i.e. set to `false`), an origin access identity will be created by default. + +#### Using OAC for 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 AWS KMS key. A statement needs to be added to the KMS key policy to give the OAC permission to use the KMS key. + +```ts +const myKmsKey = 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: new origins.S3Origin(myBucket) // Automatically creates an OAC + }, +}); +``` + +--- + +Ticking the box below indicates that the public API of this RFC has been +signed-off by the API bar raiser (the `status/api-approved` label was applied to the +RFC pull request): + +``` +[ ] Signed-off by API Bar Raiser @colifran +``` + +## Public FAQ + +### What are we launching today? + +We are launching a new L2 construct `OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`). We are also launching some modifications to the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module. + +### Why should I use this feature? + +With this new feature, you can follow AWS best practices of using IAM service principals to authenticate with your AWS origin. This ensures users only access the content in your AWS origin through your specified CloudFront distribution. OAC also supports new AWS regions launched after December 2022 and S3 origins that use SSE-KMS encryption. + +## Internal FAQ + +### Why are we doing this? + +This feature has been highly requested by the community since August 2022 when Origin Access Control was launched (195 upvotes on the [GitHub issue](https://github.com/aws/aws-cdk/issues/21771)). Although the L1 construct `CfnOriginAccessControl` exists, users currently need to remove the OAI automatically configured by the existing `S3Origin` construct which is a subpar user experience. We want to make it easier for users to follow AWS best practices and secure their CloudFront origins. + +### Why should we _not_ do this? + +Users who want to use OAC may have already found workarounds using the L1 construct. + +### What is the technical solution (design) of this feature? + +This feature will be introduced under a feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` as the current default configuration for S3 origins using OAI is still supported. + +#### New `OriginAccessControl` L2 Construct + +```ts +export interface IOriginAccessControl extends IResource { + /** + * The unique identifier of the origin access control. + * @attribute + */ + readonly originAccessControlId: string; +} + +abstract class OriginAccessControlBase extends Resource implements IOriginAccessControl { + /** + * The unique identifier of the origin access control. + * @attribute + */ + public abstract readonly originAccessControlId: string; +} + +/** + * Properties for creating a OriginAccessControl resource. + */ +export interface OriginAccessControlProps { + /** + * A description of the origin access control. + * @default - no description + */ + readonly description?: string; + /** + * A name to identify the origin access control. You can specify up to 64 characters. + * @default - a generated name + */ + readonly originAccessControlName?: string; + /** + * The type of origin that this origin access control is for. + * @default s3 + */ + readonly originAccessControlOriginType?: OriginAccessControlOriginType; + /** + * Specifies which requests CloudFront signs. + * @default always + */ + readonly signingBehavior?: SigningBehavior; + /** + * The signing protocol of the origin access control. + * @default sigv4 + */ + readonly signingProtocol?: SigningProtocol; +} + +/** + * Origin types supported by origin access control. + */ +export enum OriginAccessControlOriginType { + /** + * Uses an Amazon S3 bucket origin. + */ + S3 = 's3', + /** + * Uses an AWS Elemental MediaStore origin. + */ + MEDIASTORE = 'mediastore', + /** + * Uses a Lambda function URL origin. + */ + LAMBDA = 'lambda', + /** + * Uses an AWS Elemental MediaPackage v2 origin. + */ + MEDIAPACKAGEV2 = 'mediapackagev2', +} + +/** + * Options for which requests CloudFront signs. + * Specify `always` for the most common use case. + */ +export enum SigningBehavior { + /** + * Sign all origin requests, overwriting the Authorization header + * from the viewer request if one exists. + */ + ALWAYS = 'always', + /** + * Do not sign any origin requests. + * This value turns off origin access control for all origins in all + * distributions that use this origin access control. + */ + NEVER = 'never', + /** + * Sign origin requests only if the viewer request + * doesn't contain the Authorization header. + */ + NO_OVERRIDE = 'no-override', +} + +/** + * The signing protocol of the origin access control. + */ +export enum SigningProtocol { + /** + * The AWS Signature Version 4 signing protocol. + */ + SIGV4 = 'sigv4', +} + +/** + * An Origin Access Control. + * @resource AWS::CloudFront::OriginAccessControl + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html + */ +export class OriginAccessControl extends OriginAccessControlBase { + /** + * Imports an origin access control from its id. + */ + public static fromOriginAccessControlId(scope: Construct, id: string, originAccessControlId: string): IOriginAccessControl { + class Import extends OriginAccessControlBase { + public readonly originAccessControlId = originAccessControlId; + constructor(s: Construct, i: string) { + super(s, i); + + this.originAccessControlId = originAccessControlId; + } + } + return new Import(scope, id); + } + + public readonly originAccessControlId: string; + constructor(scope: Construct, id: string, props: OriginAccessControlProps = {}) { + super(scope, id); + + const resource = new CfnOriginAccessControl(this, 'Resource', { + originAccessControlConfig: { + description: props.description, + name: props.originAccessControlName ?? this.generateName(), + signingBehavior: props.signingBehavior ?? SigningBehavior.ALWAYS, + signingProtocol: props.signingProtocol ?? SigningProtocol.SIGV4, + originAccessControlOriginType: props.originAccessControlOriginType ?? OriginAccessControlOriginType.S3, + }, + }); + + this.originAccessControlId = resource.attrId; + } + + private generateName(): string { + const name = Stack.of(this).region + Names.uniqueId(this); + if (name.length > 64) { + return name.substring(0, 32) + name.substring(name.length - 32); + } + return name; + } + +} +``` +#### New `S3BucketOacOrigin` class + +A new class `S3BucketOacOrigin` will implement the `bind()` method to setup the OAC and update the bucket policy. + +In the case where an imported bucket is being used for the S3 origin, calling `bucket.addToResourcePolicy()` will fail to add the policy statement. Existing [workarounds](https://github.com/aws/aws-cdk/issues/6548#issuecomment-869091553) require the user to create a new `BucketPolicy` for the bucket and add the policy statements using `bucketPolicy.document.addStatements()`. However, this overwrites the whole bucket policy instead of appending statements to the existing policy which is a subpar user experience. The proposed solution to this issue is to use a custom resource to retrieve the existing bucket policy and append the OAC policy statement via the `GetBucketPolicy()` and `PutBucketPolicy()` API calls after the CloudFront distribution has been created. + +In the case where the S3 bucket uses SSE-KMS encryption (customer-managed key), a circular dependency error occurs when trying to deploy the template. When granting the CloudFront distribution access to use the KMS Key, there is a circular dependency: +- CloudFront distribution references the S3 bucket +- S3 bucket references the KMS key +- KMS Key references the CloudFront distribution + +The proposed solution to this issue is to use a custom resource to retrieve and update the KMS key policy after the CloudFront distribution has been created via the `GetKeyPolicy()` and `PutKeyPolicy()` API calls. + + +```ts +class S3BucketOacOrigin extends cloudfront.OriginBase { + private originAccessControl!: cloudfront.IOriginAccessControl; + + constructor(private readonly bucket: s3.IBucket, { originAccessControl, ...props }: S3OriginProps) { + super(bucket.bucketRegionalDomainName, props); + if (originAccessControl) { + this.originAccessControl = originAccessControl; + } + } + + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + if (!this.originAccessControl) { + // Create a new origin access control if not specified + this.originAccessControl = new cloudfront.OriginAccessControl(scope, 'S3OriginAccessControl'); + } + const distribution = scope.node.scope as cloudfront.Distribution; + const distributionId = Lazy.string({ produce: () => distribution.distributionId }); + const oacReadOnlyBucketPolicyStatement = new iam.PolicyStatement( + { + sid: 'AllowS3OACAccess', + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + actions: ['s3:GetObject'], + resources: [this.bucket.arnForObjects('*')], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:${Aws.PARTITION}:cloudfront::${Aws.ACCOUNT_ID}:distribution/${distributionId}`, + }, + }, + }, + ); + const result = this.bucket.addToResourcePolicy(oacReadOnlyBucketPolicyStatement); + + // Failed to update bucket policy, assume using imported bucket + if (!result.statementAdded) { + Annotations.of(scope).addWarningV2('@aws-cdk/aws-cloudfront-origins:updateBucketPolicy', 'Cannot update bucket policy of an imported bucket. Update the policy manually instead.'); + const provider = S3OriginAccessControlBucketPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, + { + description: 'Lambda function that updates S3 bucket policy to allow CloudFront distribution access.', + }); + provider.addToRolePolicy({ + Action: ['s3:getBucketPolicy', 's3:putBucketPolicy'], + Effect: 'Allow', + Resource: [this.bucket.bucketArn], + }); + + new CustomResource(scope, 'S3OriginBucketPolicyCustomResource', { + resourceType: S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + DistributionId: distributionId, + AccountId: this.bucket.env.account, + Partition: Stack.of(scope).partition, + BucketName: this.bucket.bucketName, + IsImportedBucket: !result.statementAdded, + }, + }); + } + + if (this.bucket.encryptionKey) { + const provider = S3OriginAccessControlKeyPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, + { + description: 'Lambda function that updates SSE-KMS key policy to allow CloudFront distribution access.', + }); + provider.addToRolePolicy({ + Action: ['kms:PutKeyPolicy', 'kms:GetKeyPolicy', 'kms:DescribeKey'], + Effect: 'Allow', + Resource: [this.bucket.encryptionKey.keyArn], + }); + + new CustomResource(scope, 'S3OriginKMSKeyPolicyCustomResource', { + resourceType: S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + DistributionId: distributionId, + KmsKeyId: this.bucket.encryptionKey.keyId, + AccountId: this.bucket.env.account, + Partition: Stack.of(scope).partition, + }, + }); + } + + const originBindConfig = super.bind(scope, options); + + // Update configuration to set OriginControlAccessId property + return { + ...originBindConfig, + originProperty: { + ...originBindConfig.originProperty!, + originAccessControlId: this.originAccessControl.originAccessControlId, + }, + }; + } + + /** + * If you're using origin access control (OAC) instead of origin access identity, specify an empty `OriginAccessIdentity` element. + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-s3originconfig.html#cfn-cloudfront-distribution-s3originconfig-originaccessidentity + */ + protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { + return { originAccessIdentity: '' }; + } +} +``` + + +#### `S3Origin` Construct Modifications + +The `S3Origin` constructor will need additional logic to determine how to configure the S3 origin (either as website endpoint, using OAI, or using OAC). + +```ts +export class S3Origin implements cloudfront.IOrigin { + private readonly origin: cloudfront.IOrigin; + + constructor(bucket: s3.IBucket, props: S3OriginProps = {}) { + if (props.originAccessControl && props.originAccessIdentity) { + throw new Error('Only one of originAccessControl or originAccessIdentity can be specified for an origin.'); + } + + if (bucket.isWebsite) { + this.origin = new HttpOrigin(bucket.bucketWebsiteDomainName, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // S3 only supports HTTP for website buckets + ...props, + }); + } else if (props.originAccessIdentity || !FeatureFlags.of(bucket.stack).isEnabled(cxapi.CLOUDFRONT_USE_ORIGIN_ACCESS_CONTROL)) { + this.origin = new S3BucketOrigin(bucket, props); + } else { + this.origin = new S3BucketOacOrigin(bucket, props); + } + } + + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + return this.origin.bind(scope, options); + } +} +``` + +#### `Distribution` construct modifications + +In the `addOrigin()` method of `Distribution`, we will need to pass the `distributionId` to `origin.bind()` to specify the condition in the policy statement. + +```ts + private addOrigin(origin: IOrigin, isFailoverOrigin: boolean = false): string { + const ORIGIN_ID_MAX_LENGTH = 128; + + const existingOrigin = this.boundOrigins.find(boundOrigin => boundOrigin.origin === origin); + if (existingOrigin) { + return existingOrigin.originGroupId ?? existingOrigin.originId; + } else { + ... + const distributionId = this.distributionId; + const originBindConfig = origin.bind(scope, { originId: generatedId, distributionId }); + ... + } + } +``` + +Policy statement with condition referencing `distributionId`: +``` +{ + "Version": "2012-10-17", + "Statement": { + "Sid": "AllowCloudFrontServicePrincipalReadOnly", + "Effect": "Allow", + "Principal": { + "Service": "cloudfront.amazonaws.com" + }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::/*", + "Condition": { + "StringEquals": { + "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/" + } + } + } +} +``` +### Deprecating `CloudFrontWebDistribution` + +This RFC proposes changes to support using OAC with the `Distribution` construct, which is the modern, improved API for creating CloudFront distributions using CDK. `CloudFrontWebDistribution` is the original construct written for working with CloudFront distributions. The CDK docs provide a [section](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront-readme.html#migrating-from-the-original-cloudfrontwebdistribution-to-the-newer-distribution-construct) to help users migrate from `CloudFrontWebDistribution` to `Distribution`, but it is not clearly stated anywhere that `CloudFrontWebDistribution` is deprecated. As OAC L2 support (and other new features) will only be provided for `Distribution` going forward, an official deprecation of `CloudFrontWebDistribution` will be part of this change. + +### Is this a breaking change? + +No, this is not a breaking change. This is a new feature and configuring S3 origins using OAI will still be supported. + + +### What is the high-level project plan? + +- [ ] Create prototype for design +- [ ] Gather feedback on the RFC +- [ ] Get bar raiser to sign off on RFC +- [ ] Implement the construct in a separate repository +- [ ] Make pull request to aws-cdk repository +- [ ] Iterate and respond to PR feedback +- [ ] Merge new construct and related changes + +### Are there any open issues that need to be addressed later? + +> Describe any major open issues that this RFC did not take into account. Once +> the RFC is approved, create GitHub issues for these issues and update this RFC +> of the project board with these issue IDs. + +Supporting Origin Access Control for Lambda Function Url origins. + +## Appendix + +- [Prototype branch](https://github.com/gracelu0/aws-cdk/tree/cloudfront-oac-l2-poc) From c506ebd645fd76764e81af7155a3ab4f5443c457 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Wed, 12 Jun 2024 23:25:42 -0700 Subject: [PATCH 3/7] fix formatting --- text/0617-cloudfront-oac-l2.md | 139 ++++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 36 deletions(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index 9950a89b5..615c5579f 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -4,9 +4,20 @@ * **Tracking Issue**: [#617](https://github.com/aws/aws-cdk-rfcs/issues/617) * **API Bar Raiser**: @colifran -[CloudFront Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) (OAC) is the recommended way to send authenticated requests to an Amazon S3 origin using IAM service principals. It offers better security, supports server-side encryption with AWS KMS, and supports all Amazon S3 buckets in all AWS regions. - -Currently the `S3Origin` construct automatically creates an Origin Access Identity (OAI) to restrict access to an S3 Origin. However, using OAI is now considered [legacy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) and no longer recommended. CDK users who want to use OAC currently have to use the L1 construct `CfnOriginAccessControl`. They need to use escape hatches to attach the OAC to their CloudFront distribution and remove the OAI that is automatically configured. With a CloudFront OAC L2 construct, users will be able to easily set up their CloudFront origins using OAC instead of OAI. +[CloudFront Origin Access Control](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) +(OAC) is the recommended way to send authenticated requests +to an Amazon S3 origin using IAM service principals. +It offers better security, supports server-side encryption with AWS KMS, +and supports all Amazon S3 buckets in all AWS regions. + +Currently the `S3Origin` construct automatically creates an Origin Access Identity (OAI) +to restrict access to an S3 Origin. However, using OAI is now considered +[legacy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) +and no longer recommended. +CDK users who want to use OAC currently have to use the L1 construct `CfnOriginAccessControl`. +They need to use escape hatches to attach the OAC to their CloudFront distribution and remove +the OAI that is automatically configured. With a CloudFront OAC L2 construct, +users will be able to easily set up their CloudFront origins using OAC instead of OAI. ## Working Backwards @@ -17,15 +28,28 @@ Currently the `S3Origin` construct automatically creates an Origin Access Identi ### README # Amazon CloudFront Construct Library -Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content, such as .html, .css, .js, and image files, to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you're serving with CloudFront, the user is routed to the edge location that provides the lowest latency, so that content is delivered with the best possible performance. + +Amazon CloudFront is a web service that speeds up distribution of your static and +dynamic web content, such as .html, .css, .js, and image files, to your users. +CloudFront delivers your content through a worldwide network of data centers called +edge locations. When a user requests content that you're serving with CloudFront, +the user is routed to the edge location that provides the lowest latency, so that +content is delivered with the best possible performance. ## Creating a Distribution -CloudFront distributions deliver your content from one or more origins; an origin is the location where you store the original version of your content. Origins can be created from S3 buckets or a custom origin (HTTP server). Constructs to define origins are in the `aws-cdk-lib/aws-cloudfront-origins` module. +CloudFront distributions deliver your content from one or more origins; an origin is +the location where you store the original version of your content. Origins can be +created from S3 buckets or a custom origin (HTTP server). Constructs +to define origins are in the `aws-cdk-lib/aws-cloudfront-origins` module. -Each distribution has a default behavior which applies to all requests to that distribution, and routes requests to a primary origin. Additional behaviors may be specified for an origin with a given URL path pattern. Behaviors allow routing with multiple origins, controlling which HTTP methods to support, whether to require users to use HTTPS, and what query strings or cookies to forward to your origin, among other settings. +Each distribution has a default behavior which applies to all requests to that +distribution, and routes requests to a primary origin. Additional behaviors may +be specified for an origin with a given URL path pattern. Behaviors allow routing +with multiple origins, controlling which HTTP methods to support, whether to require +users to use HTTPS, and what query strings or cookies to forward to your origin, among other settings. -#### From an S3 Bucket +### From an S3 Bucket An S3 bucket can be added as an origin. If the bucket is configured as a website endpoint, the distribution can use S3 redirects and S3 custom error documents. @@ -44,10 +68,14 @@ CloudFront's redirect and error handling will be used. ## Restricting access to an S3 origin -CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC is the recommended option and OAI is considered legacy (see [Restricting access to an Amazon S3 Origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). These 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. +CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: +origin access control (OAC) and origin access identity (OAI). +OAC is the recommended option and OAI is considered legacy +(see [Restricting access to an Amazon S3 Origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). +These 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. -> Note: OAC and OAI can only be used with an regular S3 bucket origin (not a bucket configured as a website endpoint). +> Note: OAC and OAI can only be used with an regular S3 bucket origin (not a bucket configured as a website endpoint). To setup origin access control for an S3 origin, you can create an `OriginAccessControl` resource and pass it into the `originAccessControl` property of the origin: @@ -63,20 +91,26 @@ new cloudfront.Distribution(this, 'myDist', { }, }); ``` -It is recommended to set the `@aws-cdk/aws-cloudfront:useOriginAccessControl` feature flag to `true`, so an OAC will be automatically created instead of an OAI when `S3Origin` is instantiated. If you don't set this feature flag, and OAI will be created and granted access to the underlying bucket. +It is recommended to set the `@aws-cdk/aws-cloudfront:useOriginAccessControl` feature flag to `true`, so an OAC will be automatically created instead +of an OAI when `S3Origin` is instantiated. If you don't set this feature flag, and OAI will be created and granted access to the underlying bucket. ## Migrating from OAI to OAC -If you are currently using OAI for your S3 origin and wish to migrate to OAC, first set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` to `true` in `cdk.json`. With this feature flag set, when you create a new `S3Origin` an Origin Access Control will be used instead of Origin Access Identity. You can create and pass in an `OriginAccessControl` or one will be automatically created by default. +If you are currently using OAI for your S3 origin and wish to migrate to OAC, first set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` +to `true` in `cdk.json`.With this feature flag set, when you create a new `S3Origin` an Origin Access Control will be used instead of Origin Access Identity. +You can create and pass in an `OriginAccessControl` or one will be automatically created by default. For more information, see [Migrating from origin access identity (OAI) to origin access control (OAC)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac). ### Using pre-existing S3 buckets -If you are using an imported bucket for your S3 Origin and want to use OAC, first import the bucket using one of the import methods (`fromBucketName`, `fromBucketArn` or `fromBucketAttributes`). +If you are using an imported bucket for your S3 Origin and want to use OAC, first import the bucket using one of the import methods (`fromBucketName`, +`fromBucketArn` or `fromBucketAttributes`). -The `S3Origin` construct will update the S3 bucket policy to allow CloudFront read-only access. If your bucket previously used OAI, there will be an attempt to remove both the policy statement that allows access to the OAI and the origin access identity itself. +The `S3Origin` construct will update the S3 bucket policy to allow CloudFront read-only access. +If your bucket previously used OAI, there will be an attempt to remove both the policy statement +that allows access to the OAI and the origin access identity itself. ```ts const bucket = s3.Bucket.fromBucketArn(this, 'MyExistingBucket', @@ -96,7 +130,6 @@ const distribution = new cloudfront.Distribution(this, 'MyDistribution', { }); ``` - # CloudFront Origins for the CDK CloudFront Library ## S3 Bucket @@ -113,15 +146,20 @@ new cloudfront.Distribution(this, 'myDist', { The above will treat the bucket differently based on if `IBucket.isWebsite` is set or not. If the bucket is configured as a website, the bucket is treated as an HTTP origin, and the built-in S3 redirects and error pages can be used. Otherwise, the bucket is handled as a bucket origin and -CloudFront's redirect and error handling will be used. +CloudFront's redirect and error handling will be used. ### Restricting access to an S3 Origin -CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). OAC is the recommended method and OAI is considered legacy (see [Restricting access to an Amazon Simple Storage Service origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). Following AWS best practices, it is recommended you set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` to `true` to use OAC by default when creating new origins. +CloudFront provides two ways to send authenticated requests to an Amazon S3 origin: origin access control (OAC) and origin access identity (OAI). +OAC is the recommended method and OAI is considered legacy (see [Restricting access to an Amazon Simple Storage Service origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). +Following AWS best practices, it is recommended you set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` to `true` to use OAC by +default when creating new origins. -For an S3 bucket that is configured as a standard S3 bucket origin (not as a website endpoint), when the above feature flag is enabled the `S3Origin` construct will automatically create an OAC and grant it access to the underlying bucket. +For an S3 bucket that is configured as a standard S3 bucket origin (not as a website endpoint), when the above feature flag is enabled the `S3Origin` +construct will automatically create an OAC and grant it access to the underlying bucket. -> [Note](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html): When you use OAC with S3 bucket origins you must set the bucket's object ownership to Bucket owner enforced, or Bucket owner preferred (only if you require ACLs). +> [Note](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html): When you use OAC with S3 +bucket origins you must set the bucket's object ownership to Bucket owner enforced, or Bucket owner preferred (only if you require ACLs). ```ts const myBucket = new s3.Bucket(this, 'myBucket', { @@ -155,7 +193,9 @@ If the feature flag is not enabled (i.e. set to `false`), an origin access ident #### Using OAC for 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 AWS KMS key. A statement needs to be added to the KMS key policy to give the OAC permission to use the KMS key. +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 AWS KMS key. +A statement needs to be added to the KMS key policy to give the OAC permission to use the KMS key. ```ts const myKmsKey = new kms.Key(this, 'myKMSKey'); @@ -185,17 +225,23 @@ RFC pull request): ### What are we launching today? -We are launching a new L2 construct `OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`). We are also launching some modifications to the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module. +We are launching a new L2 construct `OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`). We are also launching some modifications to +the existing`S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module. ### Why should I use this feature? -With this new feature, you can follow AWS best practices of using IAM service principals to authenticate with your AWS origin. This ensures users only access the content in your AWS origin through your specified CloudFront distribution. OAC also supports new AWS regions launched after December 2022 and S3 origins that use SSE-KMS encryption. +With this new feature, you can follow AWS best practices of using IAM service principals to authenticate with your AWS origin. This ensures users only +access the content in your AWS origin through your specified CloudFront distribution. OAC also supports new AWS regions launched after December 2022 +and S3 origins that use SSE-KMS encryption. ## Internal FAQ ### Why are we doing this? -This feature has been highly requested by the community since August 2022 when Origin Access Control was launched (195 upvotes on the [GitHub issue](https://github.com/aws/aws-cdk/issues/21771)). Although the L1 construct `CfnOriginAccessControl` exists, users currently need to remove the OAI automatically configured by the existing `S3Origin` construct which is a subpar user experience. We want to make it easier for users to follow AWS best practices and secure their CloudFront origins. +This feature has been highly requested by the community since August 2022 when Origin Access Control was launched (195 upvotes on the +[GitHub issue](https://github.com/aws/aws-cdk/issues/21771)). Although the L1 construct `CfnOriginAccessControl` exists, users currently need to remove +the OAI automatically configured by the existing `S3Origin` construct which is a subpar user experience. We want to make it easier for users to follow +AWS best practices and secure their CloudFront origins. ### Why should we _not_ do this? @@ -203,7 +249,8 @@ Users who want to use OAC may have already found workarounds using the L1 constr ### What is the technical solution (design) of this feature? -This feature will be introduced under a feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` as the current default configuration for S3 origins using OAI is still supported. +This feature will be introduced under a feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` as the current default configuration +for S3 origins using OAI is still supported. #### New `OriginAccessControl` L2 Construct @@ -358,19 +405,31 @@ export class OriginAccessControl extends OriginAccessControlBase { } ``` + #### New `S3BucketOacOrigin` class -A new class `S3BucketOacOrigin` will implement the `bind()` method to setup the OAC and update the bucket policy. +A new class `S3BucketOacOrigin` will implement the `bind()` method to setup the OAC and update the bucket policy. + +In the case where an imported bucket is being used for the S3 origin, calling `bucket.addToResourcePolicy()` will fail to add the policy statement. Existing +[workarounds](https://github.com/aws/aws-cdk/issues/6548#issuecomment-869091553) require the user to create a new `BucketPolicy` for the bucket and +add the policy statements using `bucketPolicy.document.addStatements()`. +However, this overwrites the whole bucket policy instead of appending statements to the +existing policy which is a subpar user experience. The proposed solution to this issue is +to use a custom resource to retrieve the existing bucket policy and append the +OAC policy statement via the `GetBucketPolicy()` and `PutBucketPolicy()` API calls +after the CloudFront distribution has been created. -In the case where an imported bucket is being used for the S3 origin, calling `bucket.addToResourcePolicy()` will fail to add the policy statement. Existing [workarounds](https://github.com/aws/aws-cdk/issues/6548#issuecomment-869091553) require the user to create a new `BucketPolicy` for the bucket and add the policy statements using `bucketPolicy.document.addStatements()`. However, this overwrites the whole bucket policy instead of appending statements to the existing policy which is a subpar user experience. The proposed solution to this issue is to use a custom resource to retrieve the existing bucket policy and append the OAC policy statement via the `GetBucketPolicy()` and `PutBucketPolicy()` API calls after the CloudFront distribution has been created. +In the case where the S3 bucket uses SSE-KMS encryption (customer-managed key), +a circular dependency error occurs when trying to deploy the template. When granting +the CloudFront distribution access to use the KMS Key, there is a circular dependency: -In the case where the S3 bucket uses SSE-KMS encryption (customer-managed key), a circular dependency error occurs when trying to deploy the template. When granting the CloudFront distribution access to use the KMS Key, there is a circular dependency: - CloudFront distribution references the S3 bucket - S3 bucket references the KMS key - KMS Key references the CloudFront distribution -The proposed solution to this issue is to use a custom resource to retrieve and update the KMS key policy after the CloudFront distribution has been created via the `GetKeyPolicy()` and `PutKeyPolicy()` API calls. - +The proposed solution to this issue is to use a custom resource +to retrieve and update the KMS key policy after the CloudFront +distribution has been created via the `GetKeyPolicy()` and `PutKeyPolicy()` API calls. ```ts class S3BucketOacOrigin extends cloudfront.OriginBase { @@ -386,7 +445,7 @@ class S3BucketOacOrigin extends cloudfront.OriginBase { public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { if (!this.originAccessControl) { // Create a new origin access control if not specified - this.originAccessControl = new cloudfront.OriginAccessControl(scope, 'S3OriginAccessControl'); + this.originAccessControl = new cloudfront.OriginAccessControl(scope,'S3OriginAccessControl'); } const distribution = scope.node.scope as cloudfront.Distribution; const distributionId = Lazy.string({ produce: () => distribution.distributionId }); @@ -408,7 +467,8 @@ class S3BucketOacOrigin extends cloudfront.OriginBase { // Failed to update bucket policy, assume using imported bucket if (!result.statementAdded) { - Annotations.of(scope).addWarningV2('@aws-cdk/aws-cloudfront-origins:updateBucketPolicy', 'Cannot update bucket policy of an imported bucket. Update the policy manually instead.'); + Annotations.of(scope).addWarningV2('@aws-cdk/aws-cloudfront-origins:updateBucketPolicy', + 'Cannot update bucket policy of an imported bucket. Update the policy manually instead.'); const provider = S3OriginAccessControlBucketPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, { description: 'Lambda function that updates S3 bucket policy to allow CloudFront distribution access.', @@ -477,7 +537,6 @@ class S3BucketOacOrigin extends cloudfront.OriginBase { } ``` - #### `S3Origin` Construct Modifications The `S3Origin` constructor will need additional logic to determine how to configure the S3 origin (either as website endpoint, using OAI, or using OAC). @@ -511,7 +570,7 @@ export class S3Origin implements cloudfront.IOrigin { #### `Distribution` construct modifications -In the `addOrigin()` method of `Distribution`, we will need to pass the `distributionId` to `origin.bind()` to specify the condition in the policy statement. +In the `addOrigin()` method of `Distribution`, we will need to pass the `distributionId` to `origin.bind()` to specify the condition in the policy statement. ```ts private addOrigin(origin: IOrigin, isFailoverOrigin: boolean = false): string { @@ -530,6 +589,7 @@ In the `addOrigin()` method of `Distribution`, we will need to pass the `distrib ``` Policy statement with condition referencing `distributionId`: + ``` { "Version": "2012-10-17", @@ -549,15 +609,22 @@ Policy statement with condition referencing `distributionId`: } } ``` + ### Deprecating `CloudFrontWebDistribution` -This RFC proposes changes to support using OAC with the `Distribution` construct, which is the modern, improved API for creating CloudFront distributions using CDK. `CloudFrontWebDistribution` is the original construct written for working with CloudFront distributions. The CDK docs provide a [section](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront-readme.html#migrating-from-the-original-cloudfrontwebdistribution-to-the-newer-distribution-construct) to help users migrate from `CloudFrontWebDistribution` to `Distribution`, but it is not clearly stated anywhere that `CloudFrontWebDistribution` is deprecated. As OAC L2 support (and other new features) will only be provided for `Distribution` going forward, an official deprecation of `CloudFrontWebDistribution` will be part of this change. +This RFC proposes changes to support using OAC with the `Distribution` construct, which is the modern, improved API for creating CloudFront +distributions using CDK. `CloudFrontWebDistribution` is the original construct +written for working with CloudFront distributions. +The CDK docs provide a +[section](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront-readme.html#migrating-from-the-original-cloudfrontwebdistribution-to-the-newer-distribution-construct) +to help users migrate from `CloudFrontWebDistribution` to `Distribution`, +but it is not clearly stated anywhere that `CloudFrontWebDistribution` is deprecated. As OAC L2 support (and other new features) will only be provided +for `Distribution` going forward, an official deprecation of `CloudFrontWebDistribution` will be part of this change. ### Is this a breaking change? No, this is not a breaking change. This is a new feature and configuring S3 origins using OAI will still be supported. - ### What is the high-level project plan? - [ ] Create prototype for design @@ -566,7 +633,7 @@ No, this is not a breaking change. This is a new feature and configuring S3 orig - [ ] Implement the construct in a separate repository - [ ] Make pull request to aws-cdk repository - [ ] Iterate and respond to PR feedback -- [ ] Merge new construct and related changes +- [ ] Merge new construct and related changes ### Are there any open issues that need to be addressed later? From ad1d43f544145358c2ecc1d344128a37818b9337 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Tue, 18 Jun 2024 13:11:30 -0700 Subject: [PATCH 4/7] refactoring from feedback --- text/0617-cloudfront-oac-l2.md | 335 +++++++++++++++++++++------------ 1 file changed, 216 insertions(+), 119 deletions(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index 615c5579f..e444f0ada 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -98,8 +98,9 @@ of an OAI when `S3Origin` is instantiated. If you don't set this feature flag, a ## Migrating from OAI to OAC If you are currently using OAI for your S3 origin and wish to migrate to OAC, first set the feature flag `@aws-cdk/aws-cloudfront:useOriginAccessControl` -to `true` in `cdk.json`.With this feature flag set, when you create a new `S3Origin` an Origin Access Control will be used instead of Origin Access Identity. -You can create and pass in an `OriginAccessControl` or one will be automatically created by default. +to `true` in `cdk.json`. With this feature flag set, when you create a new `S3Origin` an Origin Access Control will be used instead of Origin Access Identity. +You can create and pass in an `OriginAccessControl` or one will be automatically created by default. Run `cdk diff` before deploying to verify the +changes to your stack. For more information, see [Migrating from origin access identity (OAI) to origin access control (OAC)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac). @@ -263,14 +264,6 @@ export interface IOriginAccessControl extends IResource { readonly originAccessControlId: string; } -abstract class OriginAccessControlBase extends Resource implements IOriginAccessControl { - /** - * The unique identifier of the origin access control. - * @attribute - */ - public abstract readonly originAccessControlId: string; -} - /** * Properties for creating a OriginAccessControl resource. */ @@ -287,17 +280,17 @@ export interface OriginAccessControlProps { readonly originAccessControlName?: string; /** * The type of origin that this origin access control is for. - * @default s3 + * @default OriginAccessControlOriginType.S3 */ readonly originAccessControlOriginType?: OriginAccessControlOriginType; /** * Specifies which requests CloudFront signs. - * @default always + * @default SigningBehavior.ALWAYS */ readonly signingBehavior?: SigningBehavior; /** * The signing protocol of the origin access control. - * @default sigv4 + * @default SigningProtocol.SIGV4 */ readonly signingProtocol?: SigningProtocol; } @@ -385,7 +378,9 @@ export class OriginAccessControl extends OriginAccessControlBase { const resource = new CfnOriginAccessControl(this, 'Resource', { originAccessControlConfig: { description: props.description, - name: props.originAccessControlName ?? this.generateName(), + name: props.originAccessControlName ?? Names.uniqueResourceName(this, { + maxLength: 64, + }), signingBehavior: props.signingBehavior ?? SigningBehavior.ALWAYS, signingProtocol: props.signingProtocol ?? SigningProtocol.SIGV4, originAccessControlOriginType: props.originAccessControlOriginType ?? OriginAccessControlOriginType.S3, @@ -394,21 +389,13 @@ export class OriginAccessControl extends OriginAccessControlBase { this.originAccessControlId = resource.attrId; } - - private generateName(): string { - const name = Stack.of(this).region + Names.uniqueId(this); - if (name.length > 64) { - return name.substring(0, 32) + name.substring(name.length - 32); - } - return name; - } - } ``` -#### New `S3BucketOacOrigin` class +#### Modifications to `S3BucketOrigin` class -A new class `S3BucketOacOrigin` will implement the `bind()` method to setup the OAC and update the bucket policy. +The `S3BucketOrigin` will have two methods, `withAccessIdentity()` and `withAccessControl`, which each return a class configured with +the corresponding method of origin access control. In the case where an imported bucket is being used for the S3 origin, calling `bucket.addToResourcePolicy()` will fail to add the policy statement. Existing [workarounds](https://github.com/aws/aws-cdk/issues/6548#issuecomment-869091553) require the user to create a new `BucketPolicy` for the bucket and @@ -417,7 +404,8 @@ However, this overwrites the whole bucket policy instead of appending statements existing policy which is a subpar user experience. The proposed solution to this issue is to use a custom resource to retrieve the existing bucket policy and append the OAC policy statement via the `GetBucketPolicy()` and `PutBucketPolicy()` API calls -after the CloudFront distribution has been created. +after the CloudFront distribution has been created. Users can choose to opt-in by setting the `overrideImportedBucketPolicy` property to `true`. +This way we don't silently modify their imported bucket policy which could lead to unintended behaviour. In the case where the S3 bucket uses SSE-KMS encryption (customer-managed key), a circular dependency error occurs when trying to deploy the template. When granting @@ -432,116 +420,225 @@ to retrieve and update the KMS key policy after the CloudFront distribution has been created via the `GetKeyPolicy()` and `PutKeyPolicy()` API calls. ```ts -class S3BucketOacOrigin extends cloudfront.OriginBase { - private originAccessControl!: cloudfront.IOriginAccessControl; +/** + * An Origin specific to a S3 bucket (not configured for website hosting). + * + * Contains additional logic around bucket permissions and origin access control (via OAI or OAC). + */ +abstract class S3BucketOrigin extends cloudfront.OriginBase { + public static withAccessIdentity(bucket: s3.IBucket, props: S3OriginProps = {}): S3BucketOrigin { + return new (class OriginAccessIdentity extends S3BucketOrigin { + private originAccessIdentity?: cloudfront.IOriginAccessIdentity; + + public constructor() { + super(bucket, props); + this.originAccessIdentity = props.originAccessIdentity; + } - constructor(private readonly bucket: s3.IBucket, { originAccessControl, ...props }: S3OriginProps) { - super(bucket.bucketRegionalDomainName, props); - if (originAccessControl) { - this.originAccessControl = originAccessControl; - } + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + if (!this.originAccessIdentity) { + // Using a bucket from another stack creates a cyclic reference with + // the bucket taking a dependency on the generated S3CanonicalUserId for the grant principal, + // and the distribution having a dependency on the bucket's domain name. + // Fix this by parenting the OAI in the bucket's stack when cross-stack usage is detected. + const bucketStack = Stack.of(this.bucket); + const bucketInDifferentStack = bucketStack !== Stack.of(scope); + const oaiScope = bucketInDifferentStack ? bucketStack : scope; + const oaiId = bucketInDifferentStack ? `${Names.uniqueId(scope)}S3Origin` : 'S3Origin'; + + this.originAccessIdentity = new cloudfront.OriginAccessIdentity(oaiScope, oaiId, { + comment: `Identity for ${options.originId}`, + }); + }; + // Used rather than `grantRead` because `grantRead` will grant overly-permissive policies. + // Only GetObject is needed to retrieve objects for the distribution. + // This also excludes KMS permissions; currently, OAI only supports SSE-S3 for buckets. + // Source: https://aws.amazon.com/blogs/networking-and-content-delivery/serving-sse-kms-encrypted-content-from-s3-using-cloudfront/ + this.bucket.addToResourcePolicy(new iam.PolicyStatement({ + resources: [this.bucket.arnForObjects('*')], + actions: ['s3:GetObject'], + principals: [this.originAccessIdentity.grantPrincipal], + })); + return this._bind(scope, options); + } + + protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { + if (!this.originAccessIdentity) { + throw new Error('Origin access identity cannot be undefined'); + } + return { originAccessIdentity: `origin-access-identity/cloudfront/${this.originAccessIdentity.originAccessIdentityId}` }; + } + })(); } - public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { - if (!this.originAccessControl) { - // Create a new origin access control if not specified - this.originAccessControl = new cloudfront.OriginAccessControl(scope,'S3OriginAccessControl'); - } - const distribution = scope.node.scope as cloudfront.Distribution; - const distributionId = Lazy.string({ produce: () => distribution.distributionId }); - const oacReadOnlyBucketPolicyStatement = new iam.PolicyStatement( - { - sid: 'AllowS3OACAccess', - effect: iam.Effect.ALLOW, - principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], - actions: ['s3:GetObject'], - resources: [this.bucket.arnForObjects('*')], - conditions: { - StringEquals: { - 'AWS:SourceArn': `arn:${Aws.PARTITION}:cloudfront::${Aws.ACCOUNT_ID}:distribution/${distributionId}`, + public static withAccessControl(bucket: s3.IBucket, props: S3OriginProps = {}): S3BucketOrigin { + return new (class OriginAccessControl extends S3BucketOrigin { + private originAccessControl?: cloudfront.IOriginAccessControl; + + constructor() { + super(bucket, props); + this.originAccessControl = props.originAccessControl; + } + + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + if (!this.originAccessControl) { + // Create a new origin access control if not specified + this.originAccessControl = new cloudfront.OriginAccessControl(scope, 'S3OriginAccessControl'); + } + const distributionId = options.distributionId; + const result = this.grantDistributionAccessToBucket(distributionId); + + // Failed to update bucket policy, assume using imported bucket + if (!result.statementAdded) { + if (props.overrideImportedBucketPolicy) { + this.grantDistributionAccessToImportedBucket(scope, distributionId); + } else { + Annotations.of(scope).addWarningV2('@aws-cdk/aws-cloudfront-origins:updateBucketPolicy', + 'Cannot update bucket policy of an imported bucket. Set overrideImportedBucketPolicy to true or update the policy manually instead.'); + } + } + + if (this.bucket.encryptionKey) { + this.grantDistributionAccessToKey(scope, distributionId, this.bucket.encryptionKey); + } + + const originBindConfig = this._bind(scope, options); + + // Update configuration to set OriginControlAccessId property + return { + ...originBindConfig, + originProperty: { + ...originBindConfig.originProperty!, + originAccessControlId: this.originAccessControl.originAccessControlId, }, - }, - }, - ); - const result = this.bucket.addToResourcePolicy(oacReadOnlyBucketPolicyStatement); - - // Failed to update bucket policy, assume using imported bucket - if (!result.statementAdded) { - Annotations.of(scope).addWarningV2('@aws-cdk/aws-cloudfront-origins:updateBucketPolicy', - 'Cannot update bucket policy of an imported bucket. Update the policy manually instead.'); - const provider = S3OriginAccessControlBucketPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, - { - description: 'Lambda function that updates S3 bucket policy to allow CloudFront distribution access.', - }); - provider.addToRolePolicy({ - Action: ['s3:getBucketPolicy', 's3:putBucketPolicy'], - Effect: 'Allow', - Resource: [this.bucket.bucketArn], - }); + }; + } - new CustomResource(scope, 'S3OriginBucketPolicyCustomResource', { - resourceType: S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - DistributionId: distributionId, - AccountId: this.bucket.env.account, - Partition: Stack.of(scope).partition, - BucketName: this.bucket.bucketName, - IsImportedBucket: !result.statementAdded, - }, - }); - } + /** + * If you're using origin access control (OAC) instead of origin access identity, specify an empty `OriginAccessIdentity` element. + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-s3originconfig.html#cfn-cloudfront-distribution-s3originconfig-originaccessidentity + */ + protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { + return { originAccessIdentity: '' }; + } + + private grantDistributionAccessToBucket(distributionId: string): iam.AddToResourcePolicyResult { + const oacReadOnlyBucketPolicyStatement = new iam.PolicyStatement( + { + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + actions: ['s3:GetObject'], + resources: [this.bucket.arnForObjects('*')], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:${Aws.PARTITION}:cloudfront::${Aws.ACCOUNT_ID}:distribution/${distributionId}`, + }, + }, + }, + ); + const result = this.bucket.addToResourcePolicy(oacReadOnlyBucketPolicyStatement); + return result; + } - if (this.bucket.encryptionKey) { - const provider = S3OriginAccessControlKeyPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, - { - description: 'Lambda function that updates SSE-KMS key policy to allow CloudFront distribution access.', + /** + * Use custom resource to update bucket policy and remove OAI policy statement if it exists + */ + private grantDistributionAccessToImportedBucket(scope: Construct, distributionId: string) { + const provider = S3OriginAccessControlBucketPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, + { + description: 'Lambda function that updates S3 bucket policy to allow CloudFront distribution access.', + }); + provider.addToRolePolicy({ + Action: ['s3:getBucketPolicy', 's3:putBucketPolicy'], + Effect: 'Allow', + Resource: [this.bucket.bucketArn], }); - provider.addToRolePolicy({ - Action: ['kms:PutKeyPolicy', 'kms:GetKeyPolicy', 'kms:DescribeKey'], - Effect: 'Allow', - Resource: [this.bucket.encryptionKey.keyArn], - }); - new CustomResource(scope, 'S3OriginKMSKeyPolicyCustomResource', { - resourceType: S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - DistributionId: distributionId, - KmsKeyId: this.bucket.encryptionKey.keyId, - AccountId: this.bucket.env.account, - Partition: Stack.of(scope).partition, - }, - }); - } + new CustomResource(scope, 'S3OriginBucketPolicyCustomResource', { + resourceType: S3_ORIGIN_ACCESS_CONTROL_BUCKET_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + DistributionId: distributionId, + AccountId: this.bucket.env.account, + Partition: Stack.of(scope).partition, + BucketName: this.bucket.bucketName, + }, + }); + } - const originBindConfig = super.bind(scope, options); + /** + * Use custom resource to update KMS key policy + */ + private grantDistributionAccessToKey(scope: Construct, distributionId: string, key: IKey) { + const provider = S3OriginAccessControlKeyPolicyProvider.getOrCreateProvider(scope, S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, + { + description: 'Lambda function that updates SSE-KMS key policy to allow CloudFront distribution access.', + }); + provider.addToRolePolicy({ + Action: ['kms:PutKeyPolicy', 'kms:GetKeyPolicy', 'kms:DescribeKey'], + Effect: 'Allow', + Resource: [key.keyArn], + }); - // Update configuration to set OriginControlAccessId property - return { - ...originBindConfig, - originProperty: { - ...originBindConfig.originProperty!, - originAccessControlId: this.originAccessControl.originAccessControlId, - }, - }; + new CustomResource(scope, 'S3OriginKMSKeyPolicyCustomResource', { + resourceType: S3_ORIGIN_ACCESS_CONTROL_KEY_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + DistributionId: distributionId, + KmsKeyId: key.keyId, + AccountId: this.bucket.env.account, + Partition: Stack.of(scope).partition, + }, + }); + } + }); } - /** - * If you're using origin access control (OAC) instead of origin access identity, specify an empty `OriginAccessIdentity` element. - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-s3originconfig.html#cfn-cloudfront-distribution-s3originconfig-originaccessidentity - */ - protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { - return { originAccessIdentity: '' }; + protected constructor(protected readonly bucket: s3.IBucket, props: S3OriginProps = {}) { + super(bucket.bucketRegionalDomainName, props); + } + + public abstract bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig; + + protected abstract renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined; + + protected _bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + return super.bind(scope, options); } } ``` #### `S3Origin` Construct Modifications -The `S3Origin` constructor will need additional logic to determine how to configure the S3 origin (either as website endpoint, using OAI, or using OAC). +To support OAC, a property `originAccessControl` will be added to `S3OriginProps`. The `S3Origin` constructor will need additional logic to determine +how to configure the S3 origin (either as website endpoint, using OAI, or using OAC). ```ts +/** + * Properties to use to customize an S3 Origin. + */ +export interface S3OriginProps extends cloudfront.OriginProps { + /** + * An optional Origin Access Identity of the origin identity cloudfront will use when calling your s3 bucket. + * + * @default - An Origin Access Identity will be created. + */ + readonly originAccessIdentity?: cloudfront.IOriginAccessIdentity; + + /** + * An optional Origin Access Control + * @default - An Origin Access Control will be created. + */ + readonly originAccessControl?: cloudfront.IOriginAccessControl; + + /** + * When set to 'true', an attempt will be made to update the bucket policy to allow the + * CloudFront distribution access. + * @default false + */ + readonly overrideImportedBucketPolicy?: boolean; +} + export class S3Origin implements cloudfront.IOrigin { private readonly origin: cloudfront.IOrigin; @@ -556,9 +653,9 @@ export class S3Origin implements cloudfront.IOrigin { ...props, }); } else if (props.originAccessIdentity || !FeatureFlags.of(bucket.stack).isEnabled(cxapi.CLOUDFRONT_USE_ORIGIN_ACCESS_CONTROL)) { - this.origin = new S3BucketOrigin(bucket, props); + this.origin = S3BucketOrigin.withAccessIdentity(bucket, props); } else { - this.origin = new S3BucketOacOrigin(bucket, props); + this.origin = S3BucketOrigin.withAccessControl(bucket, props); } } @@ -582,7 +679,7 @@ In the `addOrigin()` method of `Distribution`, we will need to pass the `distrib } else { ... const distributionId = this.distributionId; - const originBindConfig = origin.bind(scope, { originId: generatedId, distributionId }); + const originBindConfig = origin.bind(scope, { originId: generatedId, distributionId: Lazy.string({ produce: () => this.distributionId }) }); ... } } From 0b1f0139c4175ba1bdcb2ad380f50521a2c08d9d Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Wed, 19 Jun 2024 13:30:11 -0700 Subject: [PATCH 5/7] Add to section about imported buckets --- text/0617-cloudfront-oac-l2.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index e444f0ada..f512a839f 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -109,7 +109,29 @@ For more information, see [Migrating from origin access identity (OAI) to origin If you are using an imported bucket for your S3 Origin and want to use OAC, first import the bucket using one of the import methods (`fromBucketName`, `fromBucketArn` or `fromBucketAttributes`). -The `S3Origin` construct will update the S3 bucket policy to allow CloudFront read-only access. +To update the bucket policy to allow CloudFront access you can set the `overrideImportedBucketPolicy` property to `true`. The `S3Origin` construct +will update the S3 bucket policy by appending the following policy statement to allow CloudFront read-only access: + +``` +{ + "Version": "2012-10-17", + "Statement": { + "Sid": "AllowCloudFrontServicePrincipalReadOnly", + "Effect": "Allow", + "Principal": { + "Service": "cloudfront.amazonaws.com" + }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::/*", + "Condition": { + "StringEquals": { + "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/" + } + } + } +} +``` + If your bucket previously used OAI, there will be an attempt to remove both the policy statement that allows access to the OAI and the origin access identity itself. @@ -125,7 +147,8 @@ const oac = new cloudfront.OriginAccessControl(this, 'MyOAC', { const distribution = new cloudfront.Distribution(this, 'MyDistribution', { defaultBehavior: { origin: new origins.S3Origin(bucket, { - originAccessControl: oac + originAccessControl: oac, + overrideImportedBucketPolicy: true }) } }); From 3be9e0b8ecca695c98ed96b33374738028d0a6d4 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Wed, 19 Jun 2024 13:58:54 -0700 Subject: [PATCH 6/7] update prototype branch --- text/0617-cloudfront-oac-l2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index f512a839f..8360e1bdb 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -765,4 +765,4 @@ Supporting Origin Access Control for Lambda Function Url origins. ## Appendix -- [Prototype branch](https://github.com/gracelu0/aws-cdk/tree/cloudfront-oac-l2-poc) +- [Prototype branch](https://github.com/gracelu0/aws-cdk/tree/oac-l2) From 058027da3e232d91d0b050b602536d41deff4eb9 Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Fri, 21 Jun 2024 10:20:14 -0700 Subject: [PATCH 7/7] add origin type and update import method --- text/0617-cloudfront-oac-l2.md | 73 +++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index 8360e1bdb..6473714e2 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -84,7 +84,7 @@ resource and pass it into the `originAccessControl` property of the origin: const myBucket = new s3.Bucket(this, 'myBucket'); const oac = new cloudfront.OriginAccessControl(this, 'myS3OAC'); new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { + defaultBehavior: { origin: new origins.S3Origin(myBucket, { originAccessControl: oac }) @@ -213,6 +213,23 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` +Alternatively, an existing origin access control can be imported: + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); +const importedOAC = cloudfront.OriginAccessControl.fromOriginAccessControlAttributes(this, 'myImportedOAC', { + originAccessControlId: 'ABC123ABC123AB', + originAccessControlOriginType: cloudfront.OriginAccessControlOriginType.S3, +}); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: new origins.S3Origin(myBucket, { + originAccessControl: importedOAC + }), + }, +}); +``` + If the feature flag is not enabled (i.e. set to `false`), an origin access identity will be created by default. #### Using OAC for a SSE-KMS encrypted S3 origin @@ -285,6 +302,11 @@ export interface IOriginAccessControl extends IResource { * @attribute */ readonly originAccessControlId: string; + /** + * The type of origin that the origin access control is for. + * @attribute + */ + readonly originAccessControlOriginType: string; } /** @@ -326,18 +348,6 @@ export enum OriginAccessControlOriginType { * Uses an Amazon S3 bucket origin. */ S3 = 's3', - /** - * Uses an AWS Elemental MediaStore origin. - */ - MEDIASTORE = 'mediastore', - /** - * Uses a Lambda function URL origin. - */ - LAMBDA = 'lambda', - /** - * Uses an AWS Elemental MediaPackage v2 origin. - */ - MEDIAPACKAGEV2 = 'mediapackagev2', } /** @@ -380,23 +390,31 @@ export enum SigningProtocol { */ export class OriginAccessControl extends OriginAccessControlBase { /** - * Imports an origin access control from its id. + * Imports an origin access control from its id and origin type. */ - public static fromOriginAccessControlId(scope: Construct, id: string, originAccessControlId: string): IOriginAccessControl { - class Import extends OriginAccessControlBase { - public readonly originAccessControlId = originAccessControlId; - constructor(s: Construct, i: string) { - super(s, i); - - this.originAccessControlId = originAccessControlId; - } + public static fromOriginAccessControlAttributes(scope: Construct, id: string, attrs: OriginAccessControlAttributes): IOriginAccessControl { + class Import extends Resource implements IOriginAccessControl { + public readonly originAccessControlId = attrs.originAccessControlId; + public readonly originAccessControlOriginType = attrs.originAccessControlOriginType; } return new Import(scope, id); } + /** + * The unique identifier of this Origin Access Control. + * @attribute + */ public readonly originAccessControlId: string; + + /** + * The type of origin that the origin access control is for. + * @attribute + */ + public readonly originAccessControlOriginType: string; + constructor(scope: Construct, id: string, props: OriginAccessControlProps = {}) { super(scope, id); + this.originAccessControlOriginType = props.originAccessControlOriginType ?? OriginAccessControlOriginType.S3; const resource = new CfnOriginAccessControl(this, 'Resource', { originAccessControlConfig: { @@ -406,7 +424,7 @@ export class OriginAccessControl extends OriginAccessControlBase { }), signingBehavior: props.signingBehavior ?? SigningBehavior.ALWAYS, signingProtocol: props.signingProtocol ?? SigningProtocol.SIGV4, - originAccessControlOriginType: props.originAccessControlOriginType ?? OriginAccessControlOriginType.S3, + originAccessControlOriginType: this.originAccessControlOriginType, }, }); @@ -417,7 +435,7 @@ export class OriginAccessControl extends OriginAccessControlBase { #### Modifications to `S3BucketOrigin` class -The `S3BucketOrigin` will have two methods, `withAccessIdentity()` and `withAccessControl`, which each return a class configured with +The `S3BucketOrigin` will have two methods, `withAccessIdentity()` and `withAccessControl()`, which each return a class configured with the corresponding method of origin access control. In the case where an imported bucket is being used for the S3 origin, calling `bucket.addToResourcePolicy()` will fail to add the policy statement. Existing @@ -508,6 +526,13 @@ abstract class S3BucketOrigin extends cloudfront.OriginBase { // Create a new origin access control if not specified this.originAccessControl = new cloudfront.OriginAccessControl(scope, 'S3OriginAccessControl'); } + + if (this.originAccessControl.originAccessControlOriginType !== cloudfront.OriginAccessControlOriginType.S3) { + throw new Error(`Origin access control for an S3 origin must have origin type + '${cloudfront.OriginAccessControlOriginType.S3}', got origin type + '${this.originAccessControl.originAccessControlOriginType}'`); + } + const distributionId = options.distributionId; const result = this.grantDistributionAccessToBucket(distributionId);