Skip to content

Commit 59cb6d0

Browse files
authored
feat(cloudfront): add PublicKey and KeyGroup L2 constructs (#12743)
@njlynch This is my humble start on creating L2 constructs for `PublicKey` and `KeyGroup` for CloudFront module. I'm going to need some guidance/mentorship as this is my first L2 construct from the scratch. I'll convert this PR to draft and I'll post some of my thoughts and ideas around this feature tomorrow. I'm trying to address feature requests in #11791. I've decided to lump `PublicKey` and `KeyGroup` features together as they seem to depend on each other. All in the good spirits of learning how to extend CDK 🍻 . Any ideas and/or constructive criticism is more than welcome... that's the best way to learn.✌️ ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 0963f78 commit 59cb6d0

File tree

9 files changed

+524
-1
lines changed

9 files changed

+524
-1
lines changed

packages/@aws-cdk/aws-cloudfront/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,40 @@ new CloudFrontWebDistribution(stack, 'ADistribution', {
520520
],
521521
});
522522
```
523+
524+
## KeyGroup & PublicKey API
525+
526+
Now you can create a key group to use with CloudFront signed URLs and signed cookies. You can add public keys to use with CloudFront features such as signed URLs, signed cookies, and field-level encryption.
527+
528+
The following example command uses OpenSSL to generate an RSA key pair with a length of 2048 bits and save to the file named `private_key.pem`.
529+
530+
```bash
531+
openssl genrsa -out private_key.pem 2048
532+
```
533+
534+
The resulting file contains both the public and the private key. The following example command extracts the public key from the file named `private_key.pem` and stores it in `public_key.pem`.
535+
536+
```bash
537+
openssl rsa -pubout -in private_key.pem -out public_key.pem
538+
```
539+
540+
Note: Don't forget to copy/paste the contents of `public_key.pem` file including `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines into `encodedKey` parameter when creating a `PublicKey`.
541+
542+
Example:
543+
544+
```ts
545+
new cloudfront.KeyGroup(stack, 'MyKeyGroup', {
546+
items: [
547+
new cloudfront.PublicKey(stack, 'MyPublicKey', {
548+
encodedKey: '...', // contents of public_key.pem file
549+
// comment: 'Key is expiring on ...',
550+
}),
551+
],
552+
// comment: 'Key group containing public keys ...',
553+
});
554+
```
555+
556+
See:
557+
558+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
559+
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html

packages/@aws-cdk/aws-cloudfront/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
export * from './cache-policy';
22
export * from './distribution';
33
export * from './geo-restriction';
4+
export * from './key-group';
45
export * from './origin';
56
export * from './origin-access-identity';
67
export * from './origin-request-policy';
8+
export * from './public-key';
79
export * from './web-distribution';
810

911
export * as experimental from './experimental';
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { IResource, Names, Resource } from '@aws-cdk/core';
2+
import { Construct } from 'constructs';
3+
import { CfnKeyGroup } from './cloudfront.generated';
4+
import { IPublicKey } from './public-key';
5+
6+
/**
7+
* Represents a Key Group
8+
*/
9+
export interface IKeyGroup extends IResource {
10+
/**
11+
* The ID of the key group.
12+
* @attribute
13+
*/
14+
readonly keyGroupId: string;
15+
}
16+
17+
/**
18+
* Properties for creating a Public Key
19+
*/
20+
export interface KeyGroupProps {
21+
/**
22+
* A name to identify the key group.
23+
* @default - generated from the `id`
24+
*/
25+
readonly keyGroupName?: string;
26+
27+
/**
28+
* A comment to describe the key group.
29+
* @default - no comment
30+
*/
31+
readonly comment?: string;
32+
33+
/**
34+
* A list of public keys to add to the key group.
35+
*/
36+
readonly items: IPublicKey[];
37+
}
38+
39+
/**
40+
* A Key Group configuration
41+
*
42+
* @resource AWS::CloudFront::KeyGroup
43+
*/
44+
export class KeyGroup extends Resource implements IKeyGroup {
45+
46+
/** Imports a Key Group from its id. */
47+
public static fromKeyGroupId(scope: Construct, id: string, keyGroupId: string): IKeyGroup {
48+
return new class extends Resource implements IKeyGroup {
49+
public readonly keyGroupId = keyGroupId;
50+
}(scope, id);
51+
}
52+
public readonly keyGroupId: string;
53+
54+
constructor(scope: Construct, id: string, props: KeyGroupProps) {
55+
super(scope, id);
56+
57+
const resource = new CfnKeyGroup(this, 'Resource', {
58+
keyGroupConfig: {
59+
name: props.keyGroupName ?? this.generateName(),
60+
comment: props.comment,
61+
items: props.items.map(key => key.publicKeyId),
62+
},
63+
});
64+
65+
this.keyGroupId = resource.ref;
66+
}
67+
68+
private generateName(): string {
69+
const name = Names.uniqueId(this);
70+
if (name.length > 80) {
71+
return name.substring(0, 40) + name.substring(name.length - 40);
72+
}
73+
return name;
74+
}
75+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { IResource, Names, Resource, Token } from '@aws-cdk/core';
2+
import { Construct } from 'constructs';
3+
import { CfnPublicKey } from './cloudfront.generated';
4+
5+
/**
6+
* Represents a Public Key
7+
*/
8+
export interface IPublicKey extends IResource {
9+
/**
10+
* The ID of the key group.
11+
* @attribute
12+
*/
13+
readonly publicKeyId: string;
14+
}
15+
16+
/**
17+
* Properties for creating a Public Key
18+
*/
19+
export interface PublicKeyProps {
20+
/**
21+
* A name to identify the public key.
22+
* @default - generated from the `id`
23+
*/
24+
readonly publicKeyName?: string;
25+
26+
/**
27+
* A comment to describe the public key.
28+
* @default - no comment
29+
*/
30+
readonly comment?: string;
31+
32+
/**
33+
* The public key that you can use with signed URLs and signed cookies, or with field-level encryption.
34+
* The `encodedKey` parameter must include `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines.
35+
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
36+
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/field-level-encryption.html
37+
*/
38+
readonly encodedKey: string;
39+
}
40+
41+
/**
42+
* A Public Key Configuration
43+
*
44+
* @resource AWS::CloudFront::PublicKey
45+
*/
46+
export class PublicKey extends Resource implements IPublicKey {
47+
48+
/** Imports a Public Key from its id. */
49+
public static fromPublicKeyId(scope: Construct, id: string, publicKeyId: string): IPublicKey {
50+
return new class extends Resource implements IPublicKey {
51+
public readonly publicKeyId = publicKeyId;
52+
}(scope, id);
53+
}
54+
55+
public readonly publicKeyId: string;
56+
57+
constructor(scope: Construct, id: string, props: PublicKeyProps) {
58+
super(scope, id);
59+
60+
if (!Token.isUnresolved(props.encodedKey) && !/^-----BEGIN PUBLIC KEY-----/.test(props.encodedKey)) {
61+
throw new Error(`Public key must be in PEM format (with the BEGIN/END PUBLIC KEY lines); got ${props.encodedKey}`);
62+
}
63+
64+
const resource = new CfnPublicKey(this, 'Resource', {
65+
publicKeyConfig: {
66+
name: props.publicKeyName ?? this.generateName(),
67+
callerReference: this.node.addr,
68+
encodedKey: props.encodedKey,
69+
comment: props.comment,
70+
},
71+
});
72+
73+
this.publicKeyId = resource.ref;
74+
}
75+
76+
private generateName(): string {
77+
const name = Names.uniqueId(this);
78+
if (name.length > 80) {
79+
return name.substring(0, 40) + name.substring(name.length - 40);
80+
}
81+
return name;
82+
}
83+
}

packages/@aws-cdk/aws-cloudfront/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@
153153
"resource-attribute:@aws-cdk/aws-cloudfront.CachePolicy.cachePolicyLastModifiedTime",
154154
"construct-interface-extends-iconstruct:@aws-cdk/aws-cloudfront.IOriginRequestPolicy",
155155
"resource-interface-extends-resource:@aws-cdk/aws-cloudfront.IOriginRequestPolicy",
156-
"resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime"
156+
"resource-attribute:@aws-cdk/aws-cloudfront.OriginRequestPolicy.originRequestPolicyLastModifiedTime",
157+
"resource-attribute:@aws-cdk/aws-cloudfront.KeyGroup.keyGroupLastModifiedTime",
158+
"resource-attribute:@aws-cdk/aws-cloudfront.PublicKey.publicKeyCreatedTime"
157159
]
158160
},
159161
"awscdkio": {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"Resources": {
3+
"AwesomePublicKeyED3E7F55": {
4+
"Type": "AWS::CloudFront::PublicKey",
5+
"Properties": {
6+
"PublicKeyConfig": {
7+
"CallerReference": "c88e460888c5762c9c47ac0cdc669370d787fb2d9f",
8+
"EncodedKey": "-----BEGIN PUBLIC KEY-----\n MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS\n JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa\n dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj\n 6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e\n 0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD\n /3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx\n NQIDAQAB\n -----END PUBLIC KEY-----\n ",
9+
"Name": "awscdkcloudfrontcustomAwesomePublicKey0E83393B"
10+
}
11+
}
12+
},
13+
"AwesomeKeyGroup3EF8348B": {
14+
"Type": "AWS::CloudFront::KeyGroup",
15+
"Properties": {
16+
"KeyGroupConfig": {
17+
"Items": [
18+
{
19+
"Ref": "AwesomePublicKeyED3E7F55"
20+
}
21+
],
22+
"Name": "awscdkcloudfrontcustomAwesomeKeyGroup73FD4DCA"
23+
}
24+
}
25+
}
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as cloudfront from '../lib';
3+
4+
const app = new cdk.App();
5+
6+
const stack = new cdk.Stack(app, 'aws-cdk-cloudfront-custom');
7+
8+
new cloudfront.KeyGroup(stack, 'AwesomeKeyGroup', {
9+
items: [
10+
new cloudfront.PublicKey(stack, 'AwesomePublicKey', {
11+
encodedKey: `-----BEGIN PUBLIC KEY-----
12+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAudf8/iNkQgdvjEdm6xYS
13+
JAyxd/kGTbJfQNg9YhInb7TSm0dGu0yx8yZ3fnpmxuRPqJIlaVr+fT4YRl71gEYa
14+
dlhHmnVegyPNjP9dNqZ7zwNqMEPOPnS/NOHbJj1KYKpn1f8pPNycQ5MQCntKGnSj
15+
6fc+nbcC0joDvGz80xuy1W4hLV9oC9c3GT26xfZb2jy9MVtA3cppNuTwqrFi3t6e
16+
0iGpraxZlT5wewjZLpQkngqYr6s3aucPAZVsGTEYPo4nD5mswmtZOm+tgcOrivtD
17+
/3sD/qZLQ6c5siqyS8aTraD6y+VXugujfarTU65IeZ6QAUbLMsWuZOIi5Jn8zAwx
18+
NQIDAQAB
19+
-----END PUBLIC KEY-----
20+
`,
21+
}),
22+
],
23+
});
24+
25+
app.synth();

0 commit comments

Comments
 (0)