Skip to content

Commit

Permalink
add section to README on migrating from OAI to OAC (#31247)
Browse files Browse the repository at this point in the history
- add section to README on migrating from OAI to OAC
- update sections in README for setting up OAI and OAC using imported
buckets (user needs to manually update the bucket policy)
- update warnings to match the correct README section title
  • Loading branch information
gracelu0 authored Aug 29, 2024
2 parents 69f10ad + 5d6d0d4 commit 8d37c8d
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 114 deletions.
305 changes: 210 additions & 95 deletions packages/aws-cdk-lib/aws-cloudfront-origins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ S3 buckets, Elastic Load Balancing v2 load balancers, or any other domain name.

## S3 Bucket

An S3 bucket can be used as an origin. An S3 bucket origin can either be configured as a standard bucket or as a website endpoint (see [Use an S3 Bucket](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html#using-s3-as-origin)).
An S3 bucket can be used as an origin. An S3 bucket origin can either be configured as a standard bucket or as a website endpoint (see AWS docs for [Using an S3 Bucket](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html#using-s3-as-origin)).

### Standard S3 Bucket

Expand Down Expand Up @@ -37,50 +37,91 @@ new cloudfront.Distribution(this, 'myDist', {

> Note: `S3Origin` has been deprecated. Use `S3BucketOrigin` for standard S3 origins and `S3StaticWebsiteOrigin` for static website S3 origins.
## Migrating from OAI to OAC
### Restricting access to S3 Origin

If you are currently using OAI for your S3 origin and wish to migrate to OAC,
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 (i.e. distribution, bucket) will be unchanged. Run `cdk diff` before deploying to verify the
changes to your stack.
CloudFront provides two ways to send authenticated requests to an Amazon S3 origin:
origin access control (OAC) and origin access identity (OAI).
OAI is considered legacy due to limited functionality and regional
limitations, whereas OAC is recommended because it supports all Amazon S3
buckets in all AWS Regions, Amazon S3 server-side encryption with AWS KMS (SSE-KMS), and dynamic requests (PUT and DELETE) to Amazon S3. Additionally,
OAC provides stronger security posture with short term credentials,
and more frequent credential rotations as compared to OAI. OAI and OAC can be used in conjunction with a bucket that is not public to
require that your users access your content using CloudFront URLs and not S3 URLs directly.

Existing setup using OAI and `S3Origin`:
See AWS docs on [Restricting access to an Amazon S3 Origin](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html) for more details.

> Note: OAC and OAI can only be used with an regular S3 bucket origin (not a bucket configured as a website endpoint).
The `S3BucketOrigin` class supports creating a S3 origin with OAC, OAI, and no access control (using your bucket access settings) via
the `withOriginAccessControl()`, `withOriginAccessIdentity()`, and `withBucketDefaults()` methods respectively.

#### Setting up a new origin access control (OAC)

Setup an S3 origin with origin access control as follows:

```ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: { origin: new origins.S3Origin(myBucket) },
defaultBehavior: {
origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically creates a S3OriginAccessControl construct
},
});
```

Updated setup using `S3BucketOrigin.withOriginAccessControl()`:
When creating a S3 origin using `origins.S3BucketOrigin.withOriginAccessControl()`, an [Origin Access Control resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-originaccesscontrol-originaccesscontrolconfig.html) is automatically created with the origin type set to `s3` and signing behavior set to `always`.

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

```ts
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(myBucket, {
originAccessLevels: [cloudfront.AccessLevel.READ, cloudfront.AccessLevel.WRITE, cloudfront.AccessLevel.DELETE],
});
```

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

```ts
const myBucket = new s3.Bucket(this, 'myBucket');
const oac = new cloudfront.S3OriginAccessControl(this, 'MyOAC', {
signing: cloudfront.Signing.SIGV4_NO_OVERRIDE
});
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(bucket, {
originAccessControl: oac
}
)
new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: { origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) },
defaultBehavior: {
origin: s3Origin
},
});
```

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).
An existing S3 origin access control can be imported using the `fromOriginAccessControlId` method:

### Using pre-existing S3 buckets
```ts
const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(this, 'myImportedOAC', {
originAccessControlId: 'ABC123ABC123AB',
});
```

> [Note](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html): When you use OAC with S3
bucket origins, the bucket's object ownership must be set to Bucket owner enforced (default for new S3 buckets), or Bucket owner preferred (only if you require ACLs).

#### Setting up OAC with imported S3 buckets

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

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

```json
{
"Version": "2012-10-17",
"Statement": {
"Sid": "GrantOACAccessToS3",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
Expand All @@ -96,60 +137,57 @@ S3 bucket to allow CloudFront read-only access
}
```

> Note: If your bucket previously used OAI, you will need to manually remove the policy statement
that gives the OAI access to your bucket from your bucket policy.

See CloudFront docs on [Giving the origin access control permission to access the S3 bucket](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#create-oac-overview-s3) for more details.

CloudFront provides two ways to send authenticated requests to an Amazon S3 origin:
origin access control (OAC) and origin access identity (OAI).
OAI is considered legacy due to limited functionality and regional
limitations, whereas OAC is recommended because it supports All Amazon S3
buckets in all AWS Regions, Amazon S3 server-side encryption with AWS KMS (SSE-KMS), and dynamic requests (PUT and DELETE) to Amazon S3. Additionally,
OAC provides stronger security posture with short term credentials,
and more frequent credential rotations as compared to OAI.
(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
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: If your bucket previously used OAI, you will need to manually remove the policy statement
that gives the OAI access to your bucket after setting up OAC.

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.
#### Using OAC for a SSE-KMS encrypted S3 origin

Setup an S3 origin with origin access control as follows:
If the objects in the S3 bucket origin are encrypted using server-side encryption with
AWS Key Management Service (SSE-KMS), the OAC must have permission to use the KMS key.
Setting up 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.

```ts
const myBucket = new s3.Bucket(this, 'myBucket');
new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: {
origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically creates an OAC
},
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,
});
```

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

```ts
const myBucket = new s3.Bucket(this, 'myBucket');
const oac = new cloudfront.S3OriginAccessControl(this, 'MyOAC', { signing: cloudfront.Signing.SIGV4_NO_OVERRIDE });
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(
bucket, { originAccessControl: oac }
)
new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: {
origin: s3Origin
origin: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically grants Distribution access to `myKmsKey`
},
});
```

An existing S3 origin access control can be imported using the `fromOriginAccessControlId` method:
If the S3 bucket has an `encryptionKey` defined, `S3BucketOrigin.withOriginAccessControl()`
will update the KMS key policy by appending the following policy statement to allow CloudFront read-only access (unless otherwise specified in the `originAccessLevels` property):

```ts
const importedOAC = cloudfront.S3OriginAccessControl.fromOriginAccessControlId(this, 'myImportedOAC', {
originAccessControlId: 'ABC123ABC123AB',
});
```json
{
"Statement": {
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:::key/<key ID>",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:cloudfront::<account ID>:distribution/*"
}
}
}
}
```

#### Setting up an OAI (legacy)

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

```ts
Expand Down Expand Up @@ -178,7 +216,38 @@ new cloudfront.Distribution(this, 'myDist', {
});
```

To setup an S3 origin with no access control:
#### Setting up OAI with imported S3 buckets (legacy)

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

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

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

See AWS docs on [Giving an origin access identity permission to read files in the Amazon S3 bucket](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) for more details.

### Setting up a S3 origin with no origin access control

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

```ts
const myBucket = new s3.Bucket(this, 'myBucket');
Expand All @@ -189,54 +258,100 @@ new cloudfront.Distribution(this, 'myDist', {
});
```

> [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).
### Migrating from OAI to OAC

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

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.
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.
Existing setup using OAI and `S3Origin`:

```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: origins.S3BucketOrigin.withOriginAccessControl(myBucket) // Automatically grants Distribution access to `myKmsKey`
},
const myBucket = new s3.Bucket(this, 'myBucket');
const s3Origin = new origins.S3Origin(myBucket);
const distribution = new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: { origin: s3Origin },
});
```

If the S3 bucket has an `encryptionKey` defined, `S3BucketOrigin.withOriginAccessControl()`
will update the KMS key policy by appending the following policy statement to allow CloudFront read-only access (unless otherwise specified in the `originAccessLevels` property):
**Step 1:**

```json
{
"Statement": {
"Sid": "GrantOACAccessToKMS",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:::key/<key ID>",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<account ID>:distribution/<CloudFront distribution ID>"
}
}
To ensure CloudFront doesn't lose access to the bucket during the transition, add a statement to bucket policy to grant OAC access to S3 origin. Deploy the stack. If you are okay with downtime during the transition, you can skip this step.

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

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

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

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

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

// Add statement to bucket policy
bucket.addToResourcePolicy(oacBucketPolicyStatement);
```

The following changes will take place:

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

**Step 2:**

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

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

```ts
const bucket = new s3.Bucket(stack, 'Bucket');
const s3Origin = origins.S3BucketOrigin.withOriginAccessControl(bucket);
const distribution = new cloudfront.Distribution(stack, 'Distribution', {
defaultBehavior: { origin: s3Origin },
});
```

The following changes will take place:

1. A `AWS::CloudFront::OriginAccessControl` resource will be created.
2. The `Origin` property of the `AWS::CloudFront::Distribution` will set [`OriginAccessControlId`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html#cfn-cloudfront-distribution-origin-originaccesscontrolid) to the OAC ID after it is created. It will also set [`S3OriginConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-s3originconfig.html#aws-properties-cloudfront-distribution-s3originconfig-properties) to `{"OriginAccessIdentity": ""}`, which deletes the origin access identity from the existing distribution.
3. The `AWS::CloudFront::CloudFrontOriginAccessIdentity` resource will be deleted.

**Will migrating from OAI to OAC cause any resource replacement?**

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

**Will migrating from OAI to OAC have any availability implications for my application?**
While the above steps follow the order recommended by CloudFront, updates to CloudFront distributions and S3 bucket policies can take some time to propagate globally. Bucket configuration updates are eventually consistent. As such, you should be aware there is a possibility of downtime.

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).

### Adding Custom Headers

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

0 comments on commit 8d37c8d

Please sign in to comment.