From f90c1d4ce20bfaa2f54afcff65053c9d3817388c Mon Sep 17 00:00:00 2001 From: gracelu0 Date: Tue, 6 Aug 2024 13:11:02 -0700 Subject: [PATCH] Refactor to use static functions for S3BucketOrigin --- text/0617-cloudfront-oac-l2.md | 246 +++++++++++++++++---------------- 1 file changed, 127 insertions(+), 119 deletions(-) diff --git a/text/0617-cloudfront-oac-l2.md b/text/0617-cloudfront-oac-l2.md index b4c6e9f93..4f85241c5 100644 --- a/text/0617-cloudfront-oac-l2.md +++ b/text/0617-cloudfront-oac-l2.md @@ -8,9 +8,9 @@ (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, including opt-in Regions launched after December 2022. +and supports all Amazon S3 buckets in all AWS regions (OAC is not supported in China and GovCloud regions). -CloudFront provides OAC for restricting access to four types of origins currently: S3 origins, Lambda function URL origins, Elemental MediaStore +CloudFront provides OAC for restricting access to four types of origins: S3 origins, Lambda function URL origins, Elemental MediaStore origins, and Elemental MediaPackage v2 origins. This RFC is scoped to adding OAC for S3 origins. See [Extending support to other origin types](#extending-support-to-other-origin-types) and @@ -64,7 +64,7 @@ An S3 bucket can be used as an origin. An S3 bucket origin can either be configu ### Standard S3 Bucket -To set up an origin using a standard S3 bucket, use the `S3BucketOriginWithOAI`, `S3BucketOriginWithOAC`, or `S3BucketOriginPublic` classes. The bucket +To set up an origin using a standard S3 bucket, use the `S3BucketOrigin` class. The bucket is handled as a bucket origin and CloudFront's redirect and error handling will be used. @@ -84,8 +84,8 @@ new cloudfront.Distribution(this, 'myDist', { ## Migrating from OAI to OAC If you are currently using OAI for your S3 origin and wish to migrate to OAC, -replace the `S3Origin` construct (now deprecated) with `S3BucketOriginWithOAC`. You can create and pass in an `S3OriginAccessControl` or one will be automatically -created by default. +replace the `S3Origin` construct (now deprecated) with `S3BucketOrigin.withOriginAccessControl()` which automatically +creates and sets up a OAC for you. The OAI will be deleted as part of the stack update. The logical IDs of the resources managed by the stack will be unchanged. Run `cdk diff` before deploying to verify the @@ -100,12 +100,12 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` -Updated setup using `S3BucketOriginWithOAC`: +Updated setup using `S3BucketOrigin.withOriginAccessControl()`: ```ts const myBucket = new s3.Bucket(this, 'myBucket'); new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { origin: new origins.S3BucketOriginWithOAC(myBucket) }, + defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) }, }); ``` @@ -149,9 +149,8 @@ CloudFront provides two ways to send authenticated requests to an Amazon S3 orig origin access control (OAC) and origin access identity (OAI). OAI is considered legacy due to limited functionality and regional limitations, whereas OAC is recommended because it supports All Amazon S3 -buckets in all AWS Regions, including opt-in Regions launched after -December 2022, Amazon S3 server-side encryption with AWS KMS (SSE-KMS), and dynamic requests (PUT and DELETE) to Amazon S3. Additionally, OAC provides -stronger security posture with short term credentials, +buckets in all AWS Regions, Amazon S3 server-side encryption with AWS KMS (SSE-KMS), and dynamic requests (PUT and DELETE) to Amazon S3. Additionally, +OAC provides stronger security posture with short term credentials, and more frequent credential rotations as compared to OAI. (see [Restricting access to an Amazon S3 Origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)). OAI and OAC can be used in conjunction with a bucket that is not public to @@ -159,48 +158,76 @@ require that your users access your content using CloudFront URLs and not S3 URL > 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 `S3OriginAccessControl` -resource and pass it into the `originAccessControl` property of the origin: +The `S3BucketOrigin` class supports creating a S3 origin with OAC, OAI, and no access control (using your bucket access settings) via +the `withOriginAccessControl()`, `withOriginAccessIdentity()`, and `withBucketDefaults()` methods respectively. + +Setup an S3 origin with origin access control as follows: ```ts const myBucket = new s3.Bucket(this, 'myBucket'); -const oac = new cloudfront.S3OriginAccessControl(this, 'myS3OAC'); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { - origin: new origins.S3BucketOriginWithOAC(myBucket, { - originAccessControl: oac - }) + origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically creates an OAC }, }); ``` -If you use `S3BucketOriginWithOAC` and do not pass in an OAC, one will automatically be created for you and attached to the distribution. +You can also customize the S3 origin access control that gets created: ```ts -const myBucket = new s3.Bucket(this, 'myBucket', { - objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_ENFORCED, +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: origins.S3BucketOrigin.withOriginAccessControl( + myBucket, { signing: cloudfront.Signing.SIGV4_NO_OVERRIDE } // Automatically creates an OAC with custom settings + ) + }, }); +``` + +An existing S3 origin access control can be imported using the `fromOriginAccessControlId` method: + +```ts +const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(this, 'myImportedOAC', { + originAccessControlId: 'ABC123ABC123AB', +}); +``` + +Setup an S3 origin with origin access identity (legacy) as follows: + +```ts +const myBucket = new s3.Bucket(this, 'myBucket'); new cloudfront.Distribution(this, 'myDist', { - defaultBehavior: { - origin: new origins.S3BucketOriginWithOAC(myBucket) // Automatically creates an OAC + defaultBehavior: { + origin: origins.S3BucketOrigin.withOriginAccessIdentity(myBucket) // Automatically creates an OAI }, }); ``` -You can also customize the S3 origin access control: +You can also pass in a custom S3 origin access identity: ```ts -const myOAC = new cloudfront.S3OriginAccessControl(this, 'myOAC', { - description: 'Origin access control for S3 origin', - signing: cloudfront.Signing.SIGV4_NEVER +const myBucket = new s3.Bucket(this, 'myBucket'); +const myOai = new cloudfront.OriginAccessIdentity(this, 'myOAI', { + comment: 'My custom OAI' +}); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: origins.S3BucketOrigin.withOriginAccessIdentity( + myBucket, myOai + ) + }, }); ``` -An existing S3 origin access control can be imported using the `fromOriginAccessControlId` method: +To setup an S3 origin with no access control: ```ts -const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(this, 'myImportedOAC', { - originAccessControlId: 'ABC123ABC123AB', +const myBucket = new s3.Bucket(this, 'myBucket'); +new cloudfront.Distribution(this, 'myDist', { + defaultBehavior: { + origin: origins.S3BucketOrigin.withBucketDefaults(myBucket) + }, }); ``` @@ -208,7 +235,8 @@ const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(t 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. -The `S3BucketOriginWithOAC` construct will automatically add the statement to the KMS key policy to give the OAC permission to use the KMS key. +Setting up an S3 origin using `S3BucketOrigin.withOriginAccessControl()` will automatically add the statement to the KMS key policy +to give the OAC permission to use the KMS key. For imported keys, you will need to manually update the key policy yourself as CDK apps cannot modify the configuration of imported resources. @@ -221,7 +249,7 @@ const myBucket = new s3.Bucket(this, 'mySSEKMSEncryptedBucket', { }); new cloudfront.Distribution(this, 'myDist', { defaultBehavior: { - origin: new origins.S3BucketOriginWithOAC(myBucket) // Automatically grants Distribution access to `myKmsKey` + origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically grants Distribution access to `myKmsKey` }, }); ``` @@ -241,16 +269,14 @@ RFC pull request): ### What are we launching today? We are launching a new L2 construct `S3OriginAccessControl` for CloudFront (`aws-cdk-lib/aws-cloudfront`) to support OAC for S3 origins. We are also -deprecating the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module and replacing it with `S3StaticWebsiteOrigin`, -`S3BucketOriginWithOAI`, `S3BucketOriginWithOAC`, -and `S3BucketOriginPublic` to provide a more transparent user experience. +deprecating the existing `S3Origin` construct in the `aws-cdk-lib/aws-cloudfront-origins` module and replacing it with `S3StaticWebsiteOrigin` and +`S3BucketOrigin` to provide a more transparent user experience. ### 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 opt-in AWS -regions launched after December 2022 and S3 origins that use SSE-KMS encryption. +specified CloudFront distribution. OAC also supports all S3 buckets in all AWS regions and S3 origins that use SSE-KMS encryption. ## Internal FAQ @@ -269,13 +295,16 @@ 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 is a set of new classes: `OriginAccessControl`, `S3BucketOriginWithOAI`, `S3BucketOriginWithOAC`, `S3BucketOriginPublic` and +This feature is a set of new classes: `OriginAccessControl`, `S3BucketOrigin` and `S3StaticWebsiteOrigin`. OAI still needs to be supported as OAC is not available in China regions. #### New `OriginAccessControl` L2 Construct The OAC class for each origin type will extend a base class `OriginAccessControlBase` and set the value of `originAccessControlOriginType` accordingly. +`S3OriginAccessControlProps` has an additional property `originAccessLevels` +to give the user flexibility for the level of +permissions (combination of READ, WRITE, DELETE) to grant OAC. ```ts /** @@ -310,10 +339,32 @@ export interface OriginAccessControlBaseProps { readonly signing?: Signing; } +export enum AccessLevel { + /** + * Grants 's3:GetObject' permission to OAC + */ + READ = 'READ', + /** + * Grants 's3:PutObject' permission to OAC + */ + WRITE = 'WRITE', + /** + * Grants 's3:DeleteObject' permission to OAC + */ + DELETE = 'DELETE', +} + /** * Properties for creating a Origin Access Control resource. */ -export interface S3OriginAccessControlProps extends OriginAccessControlBaseProps {} +export interface S3OriginAccessControlProps extends OriginAccessControlBaseProps { + /** + * The level of permissions granted in the bucket policy and key policy (if applicable) + * to the CloudFront distribution. + * @default AccessLevel.READ + */ + readonly originAccessLevels?: AccessLevel[]; +} /** * Origin types supported by Origin Access Control. @@ -378,15 +429,15 @@ export class Signing { * Sign all origin requests using the AWS Signature Version 4 signing protocol. */ public static readonly SIGV4_ALWAYS = new Signing(SigningProtocol.SIGV4, SigningBehavior.ALWAYS); - /** - * Do not sign any origin requests. - */ - public static readonly SIGV4_NEVER = new Signing(SigningProtocol.SIGV4, SigningBehavior.NEVER); /** * Sign only if the viewer request doesn't contain the Authorization header * using the AWS Signature Version 4 signing protocol. */ public static readonly SIGV4_NO_OVERRIDE = new Signing(SigningProtocol.SIGV4, SigningBehavior.NO_OVERRIDE); + /** + * Do not sign any origin requests. + */ + public static readonly NEVER = new Signing(SigningProtocol.SIGV4, SigningBehavior.NEVER); /** * The signing protocol @@ -407,6 +458,7 @@ export class Signing { * An Origin Access Control. * @resource AWS::CloudFront::OriginAccessControl * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html + * @internal */ export abstract class OriginAccessControlBase extends Resource implements IOriginAccessControl { /** @@ -485,16 +537,18 @@ This class couples two separate origin types which creates a confusing user expe the `S3Origin` class currently creates an OAI by default for standard S3 bucket origins. We would have to maintain this default for backwards compatibility, even though it is no longer the best AWS practice. -The proposal is to deprecate `S3Origin` and replace it with several classes: `S3StaticWebsiteOrigin` for static website endpoints and -`S3BucketOriginWithOAI`, `S3BucketOriginWithOAC`, or `S3BucketOriginPublic` for standard S3 bucket origins. +The proposal is to deprecate `S3Origin` and replace it with two classes: `S3StaticWebsiteOrigin` for static website endpoints and +`S3BucketOrigin` for standard S3 bucket origins. #### S3 Bucket Origin The `S3BucketOrigin` class will be an abstract class used to set up standard S3 bucket origins with various access control options: OAI, OAC and -public access. Each access control option will have its own subclass which implements the `bind()` method. The `bind()` method binds the origin to the +no origin access control (uses S3 bucket configuration only). The proposal is to create separate public static functions `withOriginAccessIdentity()`, +`withOriginAccessControl()`, and `withBucketDefaults()` for OAI, OAC and no origin access controls respectively. Each function instantiates its own +class which implements the `bind()` method. The `bind()` method binds the origin to the associated Distribution, configuring the `Origin` [property](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html) -and granting permissions to the S3 bucket. The proposal is to create separate subclasses for OAI, OAC and public access because they update the bucket -policy and `S3OriginConfig` property differently. This reduces coupling and provides more flexibility if there are future changes to S3 bucket +and granting permissions to the S3 bucket. Since each origin access resource updates +the bucket policy and `S3OriginConfig` property differently, this reduces coupling and provides more flexibility if there are future changes to S3 bucket origins. It also ensures users can only configure either OAI or OAC on their origin—configuring both is not allowed and will fail during deployment. * `S3BucketOrigin` — abstract base class @@ -505,91 +559,45 @@ interface S3OriginBaseProps extends cloudfront.OriginProps { } abstract class S3BucketOrigin extends cloudfront.OriginBase { - constructor(props: S3OriginBaseProps) { - super(props.bucket.bucketRegionalDomainName, props); + public static withOriginAccessControl(bucket: IBucket, props?: cloudfront.S3OriginAccessControlProps): cloudfront.IOrigin { + return new class extends S3BucketOrigin { + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + // Create OAC, update bucket policy and return bind configuration + } + }(); } - protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { - return { originAccessIdentity: '' }; + public static withOriginAccessIdentity(bucket: IBucket, originAccessIdentity?: cloudfront.IOriginAccessIdentity): cloudfront.IOrigin { + return new class extends S3BucketOrigin { + public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + // Setup OAI and return bind configuration + } + }(); } -} -``` -* `S3BucketOriginPublic` — subclass to define a S3 origin with public access - -```ts -interface S3BucketOriginPublicProps extends S3OriginBaseProps {} + public static withBucketDefaults(bucket: IBucket, props?: cloudfront.OriginProps): cloudfront.IOrigin { + return new class extends S3BucketOrigin { + constructor() { + super({bucket, ...props}); + } + }(); + } -class S3BucketOriginPublic extends S3BucketOrigin { constructor(props: S3OriginBaseProps) { - super(props); + super(props.bucket.bucketRegionalDomainName, props); } -} -``` - -* `S3BucketOriginWithOAI` — subclass to define a S3 origin with OAI - -```ts -interface S3BucketOriginWithOAIProps extends S3OriginBaseProps { - /** - * An optional Origin Access Identity - * @default - an Origin Access Identity will be created. - */ - readonly originAccessIdentity?: cloudfront.IOriginAccessIdentity; -} - -class S3BucketOriginWithOAI extends S3BucketOrigin { - constructor(props: S3BucketOriginWithOAIProps) {} - - public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig {} -} -``` - -* `S3BucketOriginWithOAC` — subclass to define a S3 origin with OAC - -```ts -interface S3BucketOriginWithOACProps extends S3OriginBaseProps { - /** - * An optional Origin Access Control - * @default - an Origin Access Control will be created. - */ - readonly originAccessControl?: cloudfront.IOriginAccessControl; - /** - * The level of permissions granted in the bucket policy and key policy (if applicable) - * to the CloudFront distribution. - * @default AccessLevel.READ - */ - readonly originAccessLevels?: AccessLevel[]; -} - -enum AccessLevel { - /** - * Grants 's3:GetObject' permission to OAC - */ - READ = 'READ', - /** - * Grants 's3:PutObject' permission to OAC - */ - WRITE = 'WRITE', - /** - * Grants 's3:DeleteObject' permission to OAC - */ - DELETE = 'DELETE', -} - -class S3BucketOriginWithOAC extends S3BucketOrigin { - constructor(props: S3BucketOriginWithOACProps) {} + protected _bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + return super.bind(scope, options); + } - public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig {} + protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { + return { originAccessIdentity: '' }; + } } ``` -An additional property `originAccessLevels` will be added to `S3BucketOriginWithOACProps` -to give the user flexibility for the level of -permissions (combination of READ, WRITE, DELETE) to grant OAC. - -In the case where the S3 bucket uses SSE-KMS encryption (customer-managed key), +For setting up OAC, when 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: