Skip to content

Commit

Permalink
Refactor to use static functions for S3BucketOrigin
Browse files Browse the repository at this point in the history
  • Loading branch information
gracelu0 committed Aug 6, 2024
1 parent 656eb86 commit b0ce6b2
Showing 1 changed file with 87 additions and 120 deletions.
207 changes: 87 additions & 120 deletions text/0617-cloudfront-oac-l2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand All @@ -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) },
});
```

Expand Down Expand Up @@ -159,42 +159,28 @@ 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:
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: new origins.S3BucketOriginWithOAC(myBucket) // Automatically creates an OAC
defaultBehavior: {
origin: origins.S3BucketOrigin.withOriginAccessControl(
myBucket, { signing: cloudfront.Signing.SIGV4_NO_OVERRIDE } // Automatically creates an OAC with custom settings
)
},
});
```

You can also customize the S3 origin access control:

```ts
const myOAC = new cloudfront.S3OriginAccessControl(this, 'myOAC', {
description: 'Origin access control for S3 origin',
signing: cloudfront.Signing.SIGV4_NEVER
});
```

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

Expand All @@ -208,7 +194,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.

Expand All @@ -221,7 +208,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`
},
});
```
Expand All @@ -241,16 +228,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
Expand All @@ -269,13 +254,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
/**
Expand Down Expand Up @@ -310,10 +298,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.
Expand Down Expand Up @@ -378,15 +388,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
Expand All @@ -407,6 +417,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 {
/**
Expand Down Expand Up @@ -485,16 +496,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
Expand All @@ -505,91 +518,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:

Expand Down

0 comments on commit b0ce6b2

Please sign in to comment.