Skip to content

Commit

Permalink
feat(aws-cloudfront-s3): update construct to use origin access contro…
Browse files Browse the repository at this point in the history
…ls; add support for CMK-encrypted buckets (#1038)

* feat: upgrade aws-cloudfront-s3 to use oac instead of oai

* feat: added testing and documentation notes

* merge: alignment with #1037

* fix: version number for aws-cloudfront-s3 custom resource

* fix: edits to custom resource package.json file

* fix: issue with installing deps for custom resource

* feat: create new S3OacOrigin, consolidate custom resources

* chore: change bucket variable naming in 'aws-cloudfront-s3/lib/index.ts'

* chore: consolidate custom resources, additional cleanup

* chore: update resources/package.json file

* chore: add resources dependency to aws-cloudfront-s3 package.json file

* chore: edit source/package.json to revert workspaces specification

* chore: add aws-cdk-lib to resources/package.json

* chore: remove carat from aws-cdk-lib dependency

* Update package.json

* chore: pr updates

* chore: pr updates

* chore: update integ tests

* chore: update package.json

* fix: oac naming and resources/package.json

* chore: add cfn suppression statements to custom resource providers where applicable

* chore: cfn-nag statements for bucket deployments

* chore: remove bucketDeployment samples

* chore: update imports and integ tests

* chore: update integ tests

* chore: updated naming for `defaults.CloudFrontDistributionForS3` function

* chore: edited comments for s3-oac-origin.ts

* chore: minor organizational improvements to import statements

* chore: update custom resource description; remove CWLogs policy statement

* chore: naming and spelling

* feat: add checking for duplicate key policies by sid

* feat: re-add support for legacy HttpOrigin configs

* chore: eslint

* chore: more eslint

* chore: cfn-nag

* feat: add printWarning for customers using HttpOrigin

* fix: remove warning prefix

* chore: add comment on default key policy name specification

* chore: additional testing, readme updates

* feat: refactored props for createCloudFrontDistributionForS3()

* chore: print warnings language updates

* chore: switch file naming for shared functions in resources/

* chore: update imports in resources/

* feat: overwrite existing key policy, update unit tests

* fix: pass id from the top level through to createCloudFrontDistributionForS3

* chore: update integration tests
  • Loading branch information
hayesry committed Jan 9, 2024
1 parent a017f27 commit 012f9e7
Show file tree
Hide file tree
Showing 42 changed files with 5,766 additions and 410 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
lib/*.js
lib/**/*.js
test/*.js
*.d.ts
coverage
test/lambda/index.js
node_modules
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cloudfronts3`|

## Overview
This AWS Solutions Construct implements an AWS CloudFront fronting an AWS S3 Bucket.
This AWS Solutions Construct provisions an Amazon CloudFront Distribution that serves objects from an AWS S3 Bucket via an Origin Access Control (OAC).

Here is a minimal deployable pattern definition:

Expand Down Expand Up @@ -71,12 +71,13 @@ new CloudFrontToS3(this, "test-cloudfront-s3", new CloudFrontToS3Props.Builder()

| **Name** | **Type** | **Description** |
|:-------------|:----------------|-----------------|
|cloudFrontWebDistribution|[`cloudfront.Distribution`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.Distribution.html)|Returns an instance of cloudfront.Distribution created by the construct|
|cloudFrontFunction?|[`cloudfront.Function`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.Function.html)|Returns an instance of the Cloudfront function created by the pattern.|
|cloudFrontLoggingBucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-s3-readme.html)|Returns an instance of the logging bucket for CloudFront Distribution.|
|s3BucketInterface|[`s3.IBucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.IBucket.html)|Returns an instance of s3.IBucket created by the construct|
|s3Bucket?|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct. IMPORTANT: If existingBucketObj was provided in Pattern Construct Props, this property will be `undefined`|
|cloudFrontWebDistribution|[`cloudfront.Distribution`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.Distribution.html)|Returns an instance of cloudfront.Distribution created by the construct.|
|cloudFrontFunction?|[`cloudfront.Function`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront.Function.html)|Returns an instance of the Cloudfront function created by the construct.|
|cloudFrontLoggingBucket|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-s3-readme.html)|Returns an instance of the logging bucket for the CloudFront Distribution.|
|s3BucketInterface|[`s3.IBucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.IBucket.html)|Returns an instance of s3.IBucket created by the construct.|
|s3Bucket?|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct. IMPORTANT: If `existingBucketObj` was provided in Pattern Construct Props, this property will be `undefined`|
|s3LoggingBucket?|[`s3.Bucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html)|Returns an instance of s3.Bucket created by the construct as the logging bucket for the primary bucket.|
|originAccessControl?|[`cloudfront.CfnOriginAccessControl`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.cloudfront.CfnOriginAccessControl.html)|Returns an instance of cloudfront.CfnOriginAccessControl created by the construct.|

## Default settings

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
* and limitations under the License.
*/

import { Aws } from 'aws-cdk-lib';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as defaults from '@aws-solutions-constructs/core';
import * as resources from '@aws-solutions-constructs/resources';
// Note: To ensure CDKv2 compatibility, keep the import statement for Construct separate
import { Construct } from 'constructs';
import * as defaults from '@aws-solutions-constructs/core';

/**
* @summary The properties for the CloudFrontToS3 Construct
Expand Down Expand Up @@ -95,6 +99,7 @@ export class CloudFrontToS3 extends Construct {
public readonly s3BucketInterface: s3.IBucket;
public readonly s3Bucket?: s3.Bucket;
public readonly s3LoggingBucket?: s3.Bucket;
public readonly originAccessControl?: cloudfront.CfnOriginAccessControl;

/**
* @summary Constructs a new instance of the CloudFrontToS3 class.
Expand All @@ -114,7 +119,7 @@ export class CloudFrontToS3 extends Construct {
defaults.CheckS3Props(props);
defaults.CheckCloudFrontProps(props);

let bucket: s3.IBucket;
let originBucket: s3.IBucket;

if (!props.existingBucketObj) {
const buildS3BucketResponse = defaults.buildS3Bucket(this, {
Expand All @@ -124,25 +129,68 @@ export class CloudFrontToS3 extends Construct {
});
this.s3Bucket = buildS3BucketResponse.bucket;
this.s3LoggingBucket = buildS3BucketResponse.loggingBucket;
bucket = this.s3Bucket;
originBucket = this.s3Bucket;
} else {
bucket = props.existingBucketObj;
originBucket = props.existingBucketObj;
}

this.s3BucketInterface = bucket;
this.s3BucketInterface = originBucket;

const cloudFrontDistributionForS3Response = defaults.CloudFrontDistributionForS3(
this,
this.s3BucketInterface,
props.cloudFrontDistributionProps,
props.insertHttpSecurityHeaders,
props.originPath,
props.cloudFrontLoggingBucketProps,
props.responseHeadersPolicyProps
);
// Define the CloudFront Distribution
const cloudFrontDistributionForS3Props: defaults.CreateCloudFrontDistributionForS3Props = {
sourceBucket: this.s3BucketInterface,
cloudFrontDistributionProps: props.cloudFrontDistributionProps,
httpSecurityHeaders: props.insertHttpSecurityHeaders,
cloudFrontLoggingBucketProps: props.cloudFrontLoggingBucketProps,
responseHeadersPolicyProps: props.responseHeadersPolicyProps
};
const cloudFrontDistributionForS3Response = defaults.createCloudFrontDistributionForS3(this, id, cloudFrontDistributionForS3Props);
this.cloudFrontWebDistribution = cloudFrontDistributionForS3Response.distribution;
this.cloudFrontFunction = cloudFrontDistributionForS3Response.cloudfrontFunction;
this.cloudFrontLoggingBucket = cloudFrontDistributionForS3Response.loggingBucket;
}
this.originAccessControl = cloudFrontDistributionForS3Response.originAccessControl;

}
// Attach the OriginAccessControl to the CloudFront Distribution, and remove the OriginAccessIdentity
const l1CloudFrontDistribution = this.cloudFrontWebDistribution.node.defaultChild as cloudfront.CfnDistribution;
l1CloudFrontDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginAccessControlId', this.originAccessControl?.attrId);
if (props.originPath) {
l1CloudFrontDistribution.addPropertyOverride('DistributionConfig.Origins.0.OriginPath', props.originPath);
}

// Grant CloudFront permission to get the objects from the s3 bucket origin
originBucket.addToResourcePolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject'],
principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
resources: [originBucket.arnForObjects('*')],
conditions: {
StringEquals: {
'AWS:SourceArn': `arn:aws:cloudfront::${Aws.ACCOUNT_ID}:distribution/${this.cloudFrontWebDistribution.distributionId}`
}
}
})
);

// We need to create a custom resource to introduce the indirection necessary to avoid
// a circular dependency when granting the CloudFront distribution access to use the
// KMS key to decrypt objects. Without this indirection, it is not possible to reference
// the CloudFront distribution ID in the KMS key policy because -
// * The S3 bucket references the KMS key
// * The CloudFront distribution references the bucket
// * The KMS key references the CloudFront distribution
let encryptionKey: kms.IKey | undefined;
if (props.bucketProps && props.bucketProps.encryptionKey) {
encryptionKey = props.bucketProps.encryptionKey;
} else if (props.existingBucketObj && props.existingBucketObj.encryptionKey) {
encryptionKey = props.existingBucketObj.encryptionKey;
}

if (encryptionKey) {
resources.createKeyPolicyUpdaterCustomResource(this, {
distribution: this.cloudFrontWebDistribution,
encryptionKey
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-solutions-constructs/core": "0.0.0",
"@aws-solutions-constructs/resources": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudfront-origins": "0.0.0",
"constructs": "^3.2.0"
Expand Down Expand Up @@ -85,6 +86,7 @@
"@aws-cdk/aws-cloudfront": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-solutions-constructs/core": "0.0.0",
"@aws-solutions-constructs/resources": "0.0.0",
"constructs": "^3.2.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-certificatemanager": "0.0.0",
Expand Down
Loading

0 comments on commit 012f9e7

Please sign in to comment.