From b793a255f369a75fbdac4c734d32f1f543429e63 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 27 Aug 2020 14:12:24 +0100 Subject: [PATCH 1/2] feat(cloudfront): support for includeBody for Lambda@Edge The `includeBody` flag enables a Lambda@Edge function to receive the body of the request or response. Enabled this flag for both the `CloudFrontWebDistribution` and `Distribution` constructs. fixes #7085 --- packages/@aws-cdk/aws-cloudfront/README.md | 1 + packages/@aws-cdk/aws-cloudfront/lib/distribution.ts | 8 ++++++++ .../aws-cloudfront/lib/private/cache-behavior.ts | 1 + packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts | 9 +++++++++ .../@aws-cdk/aws-cloudfront/test/distribution.test.ts | 2 ++ .../aws-cloudfront/test/web_distribution.test.ts | 2 ++ 6 files changed, 23 insertions(+) diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index ed8cb9d5eaea2..1d63e9ee968bd 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -207,6 +207,7 @@ new cloudfront.Distribution(this, 'myDist', { { functionVersion: myFunc.currentVersion, eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, // Optional - defaults to false }, ], }, diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index a53d38fb054be..851e8b1f3e3c6 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -621,6 +621,14 @@ export interface EdgeLambda { /** The type of event in response to which should the function be invoked. */ readonly eventType: LambdaEdgeEventType; + + /** + * Allows a Lambda function to have read access to the body content. + * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html + * + * @default false + */ + readonly includeBody?: boolean; } /** diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 18b4d75aac2c7..fcf55c168d2bd 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -51,6 +51,7 @@ export class CacheBehavior { ? this.props.edgeLambdas.map(edgeLambda => ({ lambdaFunctionArn: edgeLambda.functionVersion.edgeArn, eventType: edgeLambda.eventType.toString(), + includeBody: edgeLambda.includeBody, })) : undefined, }; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 4a4ebcede0b9b..090bf9f7e12f1 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -424,6 +424,14 @@ export interface LambdaFunctionAssociation { * A version of the lambda to associate */ readonly lambdaFunction: lambda.IVersion; + + /** + * Allows a Lambda function to have read access to the body content. + * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html + * + * @default false + */ + readonly includeBody?: boolean; } export interface ViewerCertificateOptions { @@ -897,6 +905,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu .map(fna => ({ eventType: fna.eventType, lambdaFunctionArn: fna.lambdaFunction && fna.lambdaFunction.edgeArn, + includeBody: fna.includeBody, })), }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 0a8b5933e7f59..7494c4728eaee 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -468,6 +468,7 @@ describe('with Lambda@Edge functions', () => { { functionVersion: lambdaFunction.currentVersion, eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, }, ], }, @@ -479,6 +480,7 @@ describe('with Lambda@Edge functions', () => { LambdaFunctionAssociations: [ { EventType: 'origin-request', + IncludeBody: true, LambdaFunctionARN: { Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index c6cd639a7262a..39bcf71c1103a 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -444,6 +444,7 @@ nodeunitShim({ lambdaFunctionAssociations: [{ eventType: LambdaEdgeEventType.ORIGIN_REQUEST, lambdaFunction: lambdaFunction.currentVersion, + includeBody: true, }], }, ], @@ -457,6 +458,7 @@ nodeunitShim({ 'LambdaFunctionAssociations': [ { 'EventType': 'origin-request', + 'IncludeBody': true, 'LambdaFunctionARN': { 'Ref': 'LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e', }, From ccb8a1aae9cea6f7cb890f8f4cb013eb97e69366 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 28 Aug 2020 10:28:50 +0100 Subject: [PATCH 2/2] Validate event type with includeBody --- .../aws-cloudfront/lib/distribution.ts | 1 + .../lib/private/cache-behavior.ts | 11 ++++++- .../aws-cloudfront/lib/web_distribution.ts | 6 ++++ .../test/private/cache-behavior.test.ts | 30 +++++++++++++++-- .../test/web_distribution.test.ts | 32 +++++++++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 851e8b1f3e3c6..117e79cc26345 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -624,6 +624,7 @@ export interface EdgeLambda { /** * Allows a Lambda function to have read access to the body content. + * Only valid for "request" event types (`ORIGIN_REQUEST` or `VIEWER_REQUEST`). * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html * * @default false diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index fcf55c168d2bd..cd364a4af5fed 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -1,5 +1,5 @@ import { CfnDistribution } from '../cloudfront.generated'; -import { AddBehaviorOptions, ViewerProtocolPolicy } from '../distribution'; +import { AddBehaviorOptions, LambdaEdgeEventType, ViewerProtocolPolicy, EdgeLambda } from '../distribution'; /** * Properties for specifying custom behaviors for origins. @@ -24,6 +24,8 @@ export class CacheBehavior { constructor(originId: string, private readonly props: CacheBehaviorProps) { this.originId = originId; + + this.validateEdgeLambdas(props.edgeLambdas); } /** @@ -56,4 +58,11 @@ export class CacheBehavior { : undefined, }; } + + private validateEdgeLambdas(edgeLambdas?: EdgeLambda[]) { + const includeBodyEventTypes = [LambdaEdgeEventType.ORIGIN_REQUEST, LambdaEdgeEventType.VIEWER_REQUEST]; + if (edgeLambdas && edgeLambdas.some(lambda => lambda.includeBody && !includeBodyEventTypes.includes(lambda.eventType))) { + throw new Error('\'includeBody\' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types.'); + } + } } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 090bf9f7e12f1..cb110dbb0361d 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -427,6 +427,7 @@ export interface LambdaFunctionAssociation { /** * Allows a Lambda function to have read access to the body content. + * Only valid for "request" event types (`ORIGIN_REQUEST` or `VIEWER_REQUEST`). * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html * * @default false @@ -900,6 +901,11 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu toReturn = Object.assign(toReturn, { pathPattern: input.pathPattern }); } if (input.lambdaFunctionAssociations) { + const includeBodyEventTypes = [LambdaEdgeEventType.ORIGIN_REQUEST, LambdaEdgeEventType.VIEWER_REQUEST]; + if (input.lambdaFunctionAssociations.some(fna => fna.includeBody && !includeBodyEventTypes.includes(fna.eventType))) { + throw new Error('\'includeBody\' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types.'); + } + toReturn = Object.assign(toReturn, { lambdaFunctionAssociations: input.lambdaFunctionAssociations .map(fna => ({ diff --git a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts index 2570e889d8587..58111d9fc2ef8 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts @@ -1,13 +1,15 @@ import '@aws-cdk/assert/jest'; +import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { AllowedMethods, CachedMethods, ViewerProtocolPolicy } from '../../lib'; +import { AllowedMethods, CachedMethods, LambdaEdgeEventType, ViewerProtocolPolicy } from '../../lib'; import { CacheBehavior } from '../../lib/private/cache-behavior'; let app: App; +let stack: Stack; beforeEach(() => { app = new App(); - new Stack(app, 'Stack', { + stack = new Stack(app, 'Stack', { env: { account: '1234', region: 'testregion' }, }); }); @@ -26,6 +28,7 @@ test('renders the minimum template with an origin and path specified', () => { }); test('renders with all properties specified', () => { + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); const behavior = new CacheBehavior('origin_id', { pathPattern: '*', allowedMethods: AllowedMethods.ALLOW_ALL, @@ -35,6 +38,11 @@ test('renders with all properties specified', () => { forwardQueryStringCacheKeys: ['user_id', 'auth'], smoothStreaming: true, viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY, + edgeLambdas: [{ + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, + functionVersion: fnVersion, + }], }); expect(behavior._renderBehavior()).toEqual({ @@ -49,5 +57,23 @@ test('renders with all properties specified', () => { }, smoothStreaming: true, viewerProtocolPolicy: 'https-only', + lambdaFunctionAssociations: [{ + lambdaFunctionArn: 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1', + eventType: 'origin-request', + includeBody: true, + }], }); }); + +test('throws if edgeLambda includeBody is set for wrong event type', () => { + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); + + expect(() => new CacheBehavior('origin_id', { + pathPattern: '*', + edgeLambdas: [{ + eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, + includeBody: true, + functionVersion: fnVersion, + }], + })).toThrow(/'includeBody' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types./); +}); diff --git a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts index 39bcf71c1103a..1464e18d2e3c2 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -547,6 +547,38 @@ nodeunitShim({ test.done(); }, + 'throws when associating a lambda with includeBody and a response event type'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); + + test.throws(() => { + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + lambdaFunctionAssociations: [{ + eventType: LambdaEdgeEventType.VIEWER_RESPONSE, + includeBody: true, + lambdaFunction: fnVersion, + }], + }, + ], + }, + ], + }); + }, /'includeBody' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types./); + + test.done(); + }, + 'distribution has a defaultChild'(test: Test) { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket');