-
Notifications
You must be signed in to change notification settings - Fork 251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(aws-cloudfront-s3): update construct to use origin access controls; add support for CMK-encrypted buckets #1038
feat(aws-cloudfront-s3): update construct to use origin access controls; add support for CMK-encrypted buckets #1038
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
@@ -0,0 +1,109 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module needs to move to resources/lib/kms-key-policy-updater-custom-resource.ts - we've already got a structure for where to put custom resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this into the /resources
folder in commit f2a7053
l1CloudFrontDistribution.addPropertyOverride('DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity', ''); | ||
|
||
// Grant CloudFront permission to get the objects from the s3 bucket origin | ||
bucket.addToResourcePolicy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's change the name of this variable to originBucket
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 7e82a23
// Define the OriginAccessControl | ||
const originAccessControl = new cloudfront.CfnOriginAccessControl(this, 'CloudFrontOac', { | ||
originAccessControlConfig: { | ||
name: `cloudfront-default-oac-${new Date().getTime().toString(16)}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't work - name will be different each time it runs and cause issues with Stack Updates. Use GeneratePhysicalName() to get a reproducible unique name based on the Stack ID
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this by using generatePhysicalName('aws-cloudfront-s3', ['oac'], 16)
to create a unique name. Tested and answered the following questions before settling on this approach:
- Does not specifying anything for
name
tell CloudFormation to give it a unique name? Didn't get that far, turns out its a required property by CDK, so that didn't work. - Does giving the OAC a general name like
cloudfront-s3-oac
tell CloudFormation to still make it unique? For example,cloudfront-s3-oac-stackNameHere-someOtherRandomValues
? No, it's a literal property specification, so if you providename: cloudfront-s3-oac
, that will be the name. No adjustments or prefixes/suffixes added by CFN. This caused issues when running integration tests because one or more OACs were named the same, and the same issue would pop up if a customer had greater than one deployment of this construct.
); | ||
|
||
// We need to create a custom resource to introduce the indirection necessary to avoid | ||
// a circular dependency when granting the cloud front distribution access to use the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to be sharing this A LOT in situations where people are discussing circular dependencies in CloudFormation, so let's change the comment to say CloudFront properly. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, updated in e0a323a
// * The S3 Bucket references the KMS Key | ||
// * The CloudFront Distribution references the Bucket | ||
// * The KMS Key references the CloudFront Distribution | ||
let encryptionKey: IKey | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A third option here needs to be enableEncryptionWithCustomerManagedKey?: boolean
to be consistent with other Constructs that offer optional custom managed key functionality. See README entry in aws-lambda-sqs for one example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pinging this comment again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed together; going to hold off on this for now as we don't have a defined design pattern for taking CMK's from the top-level for use with encrypting buckets. Users can continue supplying their CMK via bucketProps
if using the construct-provided bucket, or supplying their own encrypted bucket (either w/ CMK or AWS managed key) via existingBucketObj
.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
@@ -21,10 +21,6 @@ | |||
## Overview | |||
This AWS Solutions Construct provisions an Amazon CloudFront Distribution that serves objects from an AWS S3 Bucket via an Origin Access Control (OAC). | |||
|
|||
> **Note:** This AWS Solutions Construct was updated in December 2023 to replace the use of Origin Access Identities (OAIs) with Origin Access Controls (OACs) for accessing objects from the Bucket. Due to the | |||
> current configuration of one or more underlying AWS CDK constructs, this Construct will still provison an OAI, but it will be "orphaned" and the OAC will be used for accessing the Bucket. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer accurate with the use of S3OacOrigin, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, content removed as a result
@@ -113,28 +113,34 @@ export interface CloudFrontDistributionForS3Response { | |||
/** | |||
* @internal This is an internal core function and should not be called directly by Solutions Constructs clients. | |||
*/ | |||
export function CloudFrontDistributionForS3( | |||
export function createCloudFrontDistributionForS3( | |||
scope: Construct, | |||
sourceBucket: s3.IBucket, | |||
cloudFrontDistributionProps?: cloudfront.DistributionProps | any, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we put the last 4 args into a CreateCloudFrontDistributionForS3Props interface? I'm not sure at what point SonarQube is going to yell at us for too many arguments, plus we use that pattern in many other locations - especially with a response interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed this in 65e8c32
if (!sourceBucket.isWebsite) { | ||
originAccessControl = new cloudfront.CfnOriginAccessControl(scope, 'CloudFrontOac', { | ||
originAccessControlConfig: { | ||
name: generatePhysicalName('aws-cloudfront-s3', ['oac'], 16), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add an ID to incoming arguments (not as part of the props interface) and include that in the physical name. Currently if a stack includes 2 instances of this construct we will have a name collision. Sent the ID sent to the construct constructor for the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed this in 65e8c32
source/patterns/@aws-solutions-constructs/core/lib/cloudfront-distribution-helper.ts
Show resolved
Hide resolved
// If the bucket is configured for website hosting, set up an HttpOrigin to support legacy clients | ||
printWarning(`Bucket ${bucket.bucketName} is being provided as an origin bucket to aws-cloudfront-s3, but currently | ||
has static website hosting settings enabled. Please review whether this is the intended configuration. If so, | ||
construct will configure an HttpOrigin to allow the distribution to access the bucket. If not, we strongly recommend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the underlying issue here that the bucket and objects need to be public? We should call that out, then specifically say "AWS strongly recommends against public settings on S3 buckets and objects"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated language in 0b48016
@@ -43,13 +44,13 @@ export function createKeyPolicyUpdaterCustomResource( | |||
lambdaFunctionProps: { | |||
runtime: lambda.Runtime.NODEJS_18_X, | |||
handler: 'index.handler', | |||
description: 'kms-key-policy-updater', | |||
description: 'Custom resource function that updates the key policy of an existing encryption key to allow CloudFront access.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
* and limitations under the License. | ||
*/ | ||
|
||
import * as lambda from 'aws-cdk-lib/aws-lambda'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
index doesn't seem like the correct name for this file - index.ts usually the top of the hierarchy in some way. This just looks like a utils file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can switch it to utils.ts
, made the update in 545e005
…tps://github.com/awslabs/aws-solutions-constructs into feature/hayesry/cloudfront-s3-oac-update-staging
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
const keyPolicy = JSON.parse(getKeyPolicyCommandResponse?.Policy); | ||
const keyPolicyStatementSid: string = 'Grant-CloudFront-Distribution-Key-Usage'; | ||
|
||
if (checkForExistingKeyPolicyStatement(keyPolicy, keyPolicyStatementSid)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the sid
already exists, but a new CMK is used, for example? We probably wouldn't want to return here in that case. Maybe the better logic would be to always overwrite the existing statement we are looking for, even if it ends up being a no-op .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed this in 7f24545
Let me know if I'm missing anything with the implementation
*/ | ||
export const checkForExistingKeyPolicyStatement = (parsedKeyPolicy: any, sid: string) => { | ||
const matches = parsedKeyPolicy.Statement.find((statement: any) => statement.Sid === sid); | ||
return matches ? true : false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit - you could just return matches
here, as its already the boolean result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 7f24545
lambdaFunctionProps: { | ||
runtime: lambda.Runtime.NODEJS_18_X, | ||
handler: 'index.handler', | ||
description: 'Custom resource function that updates the key policy of an existing encryption key to allow CloudFront access.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the word existing
here accurate? What if its a new CMK being provisioned as part of the construct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Touched this up in 7f24545
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
props.insertHttpSecurityHeaders, | ||
props.cloudFrontLoggingBucketProps, | ||
props.responseHeadersPolicyProps | ||
'cloudfront-dist-for-s3', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing the ID gives us no benefit if we pass a constant value, we'll still get a name collision if we launch multiple constructs in 1 stack. The idea was to pass the id of this instance of the construct (the id argument to the constructor).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That... makes more sense. Switching it now.
This comment was marked as outdated.
This comment was marked as outdated.
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Issue #, if available:
#831 #979
Description of changes:
This pull request updates the aws-cloudfront-s3 construct to use an origin access control (OAC) instead of an origin access identity (OAI) for accessing bucket that is either provisioned by or provided to the construct. This pull request also resolves an issue with the construct in working with CMK-encrypted buckets by adding a custom resource that updates the key policy whenever 1.) a CMK
encryptionKey
is provided as abucketProp
to be provisioned by the construct; or 2.) an existing bucket that is encrypted with a CMK is provided to the construct using theexistingBucketObj
property.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.