Skip to content

Commit

Permalink
feat(cloudfront): support includeBody for Lambda@Edge (#10008)
Browse files Browse the repository at this point in the history
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

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
njlynch committed Aug 28, 2020
1 parent d10b829 commit 9ffb268
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cloudfront/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ new cloudfront.Distribution(this, 'myDist', {
{
functionVersion: myFunc.currentVersion,
eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
includeBody: true, // Optional - defaults to false
},
],
},
Expand Down
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,15 @@ 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.
* 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
*/
readonly includeBody?: boolean;
}

/**
Expand Down
11 changes: 10 additions & 1 deletion packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as iam from '@aws-cdk/aws-iam';
import { CfnDistribution } from '../cloudfront.generated';
import { AddBehaviorOptions, EdgeLambda, ViewerProtocolPolicy } from '../distribution';
import { AddBehaviorOptions, EdgeLambda, LambdaEdgeEventType, ViewerProtocolPolicy } from '../distribution';

/**
* Properties for specifying custom behaviors for origins.
Expand All @@ -26,6 +26,7 @@ export class CacheBehavior {
constructor(originId: string, private readonly props: CacheBehaviorProps) {
this.originId = originId;

this.validateEdgeLambdas(props.edgeLambdas);
this.grantEdgeLambdaFunctionExecutionRole(props.edgeLambdas);
}

Expand Down Expand Up @@ -54,11 +55,19 @@ export class CacheBehavior {
? this.props.edgeLambdas.map(edgeLambda => ({
lambdaFunctionArn: edgeLambda.functionVersion.edgeArn,
eventType: edgeLambda.eventType.toString(),
includeBody: edgeLambda.includeBody,
}))
: 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.');
}
}

private grantEdgeLambdaFunctionExecutionRole(edgeLambdas?: EdgeLambda[]) {
if (!edgeLambdas || edgeLambdas.length === 0) { return; }
edgeLambdas.forEach((edgeLambda) => {
Expand Down
15 changes: 15 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,15 @@ 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.
* 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
*/
readonly includeBody?: boolean;
}

export interface ViewerCertificateOptions {
Expand Down Expand Up @@ -932,11 +941,17 @@ 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 => ({
eventType: fna.eventType,
lambdaFunctionArn: fna.lambdaFunction && fna.lambdaFunction.edgeArn,
includeBody: fna.includeBody,
})),
});

Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ describe('with Lambda@Edge functions', () => {
{
functionVersion: lambdaFunction.currentVersion,
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
includeBody: true,
},
],
},
Expand All @@ -479,6 +480,7 @@ describe('with Lambda@Edge functions', () => {
LambdaFunctionAssociations: [
{
EventType: 'origin-request',
IncludeBody: true,
LambdaFunctionARN: {
Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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' },
});
});
Expand All @@ -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,
Expand 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({
Expand All @@ -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./);
});
34 changes: 34 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ nodeunitShim({
lambdaFunctionAssociations: [{
eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
lambdaFunction: lambdaFunction.currentVersion,
includeBody: true,
}],
},
],
Expand All @@ -457,6 +458,7 @@ nodeunitShim({
'LambdaFunctionAssociations': [
{
'EventType': 'origin-request',
'IncludeBody': true,
'LambdaFunctionARN': {
'Ref': 'LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e',
},
Expand Down Expand Up @@ -545,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');
Expand Down

0 comments on commit 9ffb268

Please sign in to comment.