Skip to content

Commit

Permalink
feat(cloudfront): add support for TrustedKeyGroups in Distribution an…
Browse files Browse the repository at this point in the history
…d CloudFrontWebDistribution (#12847)


@njlynch Closes #11791

https://media3.giphy.com/media/3o7aCWJavAgtBzLWrS/giphy.gif

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
robertd authored Feb 4, 2021
1 parent 12c224a commit 349a6e2
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 16 deletions.
76 changes: 70 additions & 6 deletions packages/@aws-cdk/aws-cloudfront/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,34 @@ new cloudfront.Distribution(this, 'myDistCustomPolicy', {
});
```

### Validating signed URLs or signed cookies with Trusted Key Groups

CloudFront Distribution now supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior.

Example:

```ts
// public key in PEM format
const pubKey = new PublicKey(stack, 'MyPubKey', {
encodedKey: publicKey,
});

const keyGroup = new KeyGroup(stack, 'MyKeyGroup', {
items: [
pubKey,
],
});

new cloudfront.Distribution(stack, 'Dist', {
defaultBehavior: {
origin: new origins.HttpOrigin('www.example.com'),
trustedKeyGroups: [
keyGroup,
],
},
});
```

### Lambda@Edge

Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers.
Expand Down Expand Up @@ -274,7 +302,7 @@ new cloudfront.Distribution(this, 'myDist', {
> **Note:** Lambda@Edge functions must be created in the `us-east-1` region, regardless of the region of the CloudFront distribution and stack.
> To make it easier to request functions for Lambda@Edge, the `EdgeFunction` construct can be used.
> The `EdgeFunction` construct will automatically request a function in `us-east-1`, regardless of the region of the current stack.
> `EdgeFunction` has the same interface as `Function` and can be created and used interchangably.
> `EdgeFunction` has the same interface as `Function` and can be created and used interchangeably.
> Please note that using `EdgeFunction` requires that the `us-east-1` region has been bootstrapped.
> See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html for more about bootstrapping regions.
Expand All @@ -289,7 +317,7 @@ const myFunc = new lambda.Function(this, 'MyFunction', {
```

If the stack is not in `us-east-1`, and you need references from different applications on the same account,
you can also set a specific stack ID for each Lamba@Edge.
you can also set a specific stack ID for each Lambda@Edge.

```ts
const myFunc1 = new cloudfront.experimental.EdgeFunction(this, 'MyFunction1', {
Expand Down Expand Up @@ -427,7 +455,7 @@ You can customize the default certificate aliases. This is intended to be used i

Example:

[create a distrubution with an default certificiate example](test/example.default-cert-alias.lit.ts)
[create a distribution with an default certificate example](test/example.default-cert-alias.lit.ts)

#### ACM certificate

Expand All @@ -438,7 +466,7 @@ For more information, see [the aws-certificatemanager module documentation](http

Example:

[create a distrubution with an acm certificate example](test/example.acm-cert-alias.lit.ts)
[create a distribution with an acm certificate example](test/example.acm-cert-alias.lit.ts)

#### IAM certificate

Expand All @@ -448,7 +476,43 @@ See [Importing an SSL/TLS Certificate](https://docs.aws.amazon.com/AmazonCloudFr

Example:

[create a distrubution with an iam certificate example](test/example.iam-cert-alias.lit.ts)
[create a distribution with an iam certificate example](test/example.iam-cert-alias.lit.ts)

### Trusted Key Groups

CloudFront Web Distributions supports validating signed URLs or signed cookies using key groups. When a cache behavior contains trusted key groups, CloudFront requires signed URLs or signed cookies for all requests that match the cache behavior.

Example:

```ts
const pubKey = new PublicKey(stack, 'MyPubKey', {
encodedKey: publicKey,
});

const keyGroup = new KeyGroup(stack, 'MyKeyGroup', {
items: [
pubKey,
],
});

new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', {
originConfigs: [
{
s3OriginSource: {
s3BucketSource: sourceBucket,
},
behaviors: [
{
isDefaultBehavior: true,
trustedKeyGroups: [
keyGroup,
],
},
],
},
],
});
```

### Restrictions

Expand Down Expand Up @@ -505,7 +569,7 @@ new CloudFrontWebDistribution(stack, 'ADistribution', {
},
failoverS3OriginSource: {
s3BucketSource: s3.Bucket.fromBucketName(stack, 'aBucketFallback', 'myoriginbucketfallback'),
originPath: '/somwhere',
originPath: '/somewhere',
originHeaders: {
'myHeader2': '21',
},
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 @@ -6,6 +6,7 @@ import { Construct } from 'constructs';
import { ICachePolicy } from './cache-policy';
import { CfnDistribution } from './cloudfront.generated';
import { GeoRestriction } from './geo-restriction';
import { IKeyGroup } from './key-group';
import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin';
import { IOriginRequestPolicy } from './origin-request-policy';
import { CacheBehavior } from './private/cache-behavior';
Expand Down Expand Up @@ -706,6 +707,14 @@ export interface AddBehaviorOptions {
* @see https://aws.amazon.com/lambda/edge
*/
readonly edgeLambdas?: EdgeLambda[];

/**
* A list of Key Groups that CloudFront can use to validate signed URLs or signed cookies.
*
* @default - no KeyGroups are associated with cache behavior
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
*/
readonly trustedKeyGroups?: IKeyGroup[];
}

/**
Expand Down
13 changes: 6 additions & 7 deletions packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ export class CacheBehavior {
originRequestPolicyId: this.props.originRequestPolicy?.originRequestPolicyId,
smoothStreaming: this.props.smoothStreaming,
viewerProtocolPolicy: this.props.viewerProtocolPolicy ?? ViewerProtocolPolicy.ALLOW_ALL,
lambdaFunctionAssociations: this.props.edgeLambdas
? this.props.edgeLambdas.map(edgeLambda => ({
lambdaFunctionArn: edgeLambda.functionVersion.edgeArn,
eventType: edgeLambda.eventType.toString(),
includeBody: edgeLambda.includeBody,
}))
: undefined,
lambdaFunctionAssociations: this.props.edgeLambdas?.map(edgeLambda => ({
lambdaFunctionArn: edgeLambda.functionVersion.edgeArn,
eventType: edgeLambda.eventType.toString(),
includeBody: edgeLambda.includeBody,
})),
trustedKeyGroups: this.props.trustedKeyGroups?.map(keyGroup => keyGroup.keyGroupId),
};
}

Expand Down
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Construct } from 'constructs';
import { CfnDistribution } from './cloudfront.generated';
import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution';
import { GeoRestriction } from './geo-restriction';
import { IKeyGroup } from './key-group';
import { IOriginAccessIdentity } from './origin-access-identity';

/**
Expand Down Expand Up @@ -347,9 +348,18 @@ export interface Behavior {
* The signers are the account IDs that are allowed to sign cookies/presigned URLs for this distribution.
*
* If you pass a non empty value, all requests for this behavior must be signed (no public access will be allowed)
* @deprecated - We recommend using trustedKeyGroups instead of trustedSigners.
*/
readonly trustedSigners?: string[];

/**
* A list of Key Groups that CloudFront can use to validate signed URLs or signed cookies.
*
* @default - no KeyGroups are associated with cache behavior
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
*/
readonly trustedKeyGroups?: IKeyGroup[];

/**
*
* The default amount of time CloudFront will cache an object.
Expand Down Expand Up @@ -932,6 +942,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
forwardedValues: input.forwardedValues || { queryString: false, cookies: { forward: 'none' } },
maxTtl: input.maxTtl && input.maxTtl.toSeconds(),
minTtl: input.minTtl && input.minTtl.toSeconds(),
trustedKeyGroups: input.trustedKeyGroups?.map(key => key.keyGroupId),
trustedSigners: input.trustedSigners,
targetOriginId: input.targetOriginId,
viewerProtocolPolicy: protoPolicy || ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"Resources": {
"MyPublicKey78071F3D": {
"Type": "AWS::CloudFront::PublicKey",
"Properties": {
"PublicKeyConfig": {
"CallerReference": "c8752fac3fe06fc93f3fbd12d7e0282d8967409e4d",
"EncodedKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS\nJAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa\ndlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj\n6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e\n0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD\n/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx\nNQIDAQAB\n-----END PUBLIC KEY-----",
"Name": "integdistributionkeygroupMyPublicKeyC0F3B115"
}
}
},
"MyKeyGroupAF22FD35": {
"Type": "AWS::CloudFront::KeyGroup",
"Properties": {
"KeyGroupConfig": {
"Items": [
{
"Ref": "MyPublicKey78071F3D"
}
],
"Name": "integdistributionkeygroupMyKeyGroupF179E01A"
}
}
},
"DistB3B78991": {
"Type": "AWS::CloudFront::Distribution",
"Properties": {
"DistributionConfig": {
"DefaultCacheBehavior": {
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"Compress": true,
"TargetOriginId": "integdistributionkeygroupDistOrigin1B9677703",
"TrustedKeyGroups": [
{
"Ref": "MyKeyGroupAF22FD35"
}
],
"ViewerProtocolPolicy": "allow-all"
},
"Enabled": true,
"HttpVersion": "http2",
"IPV6Enabled": true,
"Origins": [
{
"CustomOriginConfig": {
"OriginProtocolPolicy": "https-only"
},
"DomainName": "www.example.com",
"Id": "integdistributionkeygroupDistOrigin1B9677703"
}
]
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as cdk from '@aws-cdk/core';
import * as cloudfront from '../lib';
import { TestOrigin } from './test-origin';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'integ-distribution-key-group');
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS
JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa
dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj
6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e
0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD
/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx
NQIDAQAB
-----END PUBLIC KEY-----`;

new cloudfront.Distribution(stack, 'Dist', {
defaultBehavior: {
origin: new TestOrigin('www.example.com'),
trustedKeyGroups: [
new cloudfront.KeyGroup(stack, 'MyKeyGroup', {
items: [
new cloudfront.PublicKey(stack, 'MyPublicKey', {
encodedKey: publicKey,
}),
],
}),
],
},
});

app.synth();
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import '@aws-cdk/assert/jest';
import * as lambda from '@aws-cdk/aws-lambda';
import { App, Stack } from '@aws-cdk/core';
import { AllowedMethods, CachedMethods, CachePolicy, LambdaEdgeEventType, OriginRequestPolicy, ViewerProtocolPolicy } from '../../lib';
import { AllowedMethods, CachedMethods, CachePolicy, KeyGroup, LambdaEdgeEventType, OriginRequestPolicy, PublicKey, ViewerProtocolPolicy } from '../../lib';
import { CacheBehavior } from '../../lib/private/cache-behavior';

let app: App;
let stack: Stack;
const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS
JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa
dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj
6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e
0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD
/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx
NQIDAQAB
-----END PUBLIC KEY-----`;

beforeEach(() => {
app = new App();
Expand All @@ -30,6 +39,15 @@ 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 pubKey = new PublicKey(stack, 'MyPublicKey', {
encodedKey: publicKey,
});
const keyGroup = new KeyGroup(stack, 'MyKeyGroup', {
items: [
pubKey,
],
});

const behavior = new CacheBehavior('origin_id', {
pathPattern: '*',
allowedMethods: AllowedMethods.ALLOW_ALL,
Expand All @@ -44,6 +62,7 @@ test('renders with all properties specified', () => {
includeBody: true,
functionVersion: fnVersion,
}],
trustedKeyGroups: [keyGroup],
});

expect(behavior._renderBehavior()).toEqual({
Expand All @@ -61,6 +80,9 @@ test('renders with all properties specified', () => {
eventType: 'origin-request',
includeBody: true,
}],
trustedKeyGroups: [
keyGroup.keyGroupId,
],
});
});

Expand Down
Loading

0 comments on commit 349a6e2

Please sign in to comment.