Skip to content
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(cloudfront): add support for TrustedKeyGroups in Distribution and CloudFrontWebDistribution #12847

Merged
merged 13 commits into from
Feb 4, 2021
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.
robertd marked this conversation as resolved.
Show resolved Hide resolved
> 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