Skip to content

Commit 17dbe5f

Browse files
feat(ec2): support KMS keys for block device mappings for both instances and launch templates (#18326)
This pullrequest will add the optional ```kmsKeyId``` property to the ```EbsDeviceOptions``` Interface. Whit this, it will be possible to specify the kmsKeyId used for encrypting the ebs volumes when launching instances. At the moment I already use this via an escape hatch in my projects, but it's not that handy as the block device mapping is an array. I don't like to only specify a kmsKeyId (= ARN) but accepting an ```kms.IKey``` in the properties would be a bigger change. fixes: #18309 *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 71601c1 commit 17dbe5f

File tree

5 files changed

+120
-3
lines changed

5 files changed

+120
-3
lines changed

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

+31
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,37 @@ new ec2.Instance(this, 'Instance', {
10511051

10521052
```
10531053

1054+
It is also possible to encrypt the block devices. In this example we will create an customer managed key encrypted EBS-backed root device:
1055+
1056+
```ts
1057+
import { Key } from '@aws-cdk/aws-kms';
1058+
1059+
declare const vpc: ec2.Vpc;
1060+
declare const instanceType: ec2.InstanceType;
1061+
declare const machineImage: ec2.IMachineImage;
1062+
1063+
const kmsKey = new Key(this, 'KmsKey')
1064+
1065+
new ec2.Instance(this, 'Instance', {
1066+
vpc,
1067+
instanceType,
1068+
machineImage,
1069+
1070+
// ...
1071+
1072+
blockDevices: [
1073+
{
1074+
deviceName: '/dev/sda1',
1075+
volume: ec2.BlockDeviceVolume.ebs(50, {
1076+
encrypted: true,
1077+
kmsKey: kmsKey,
1078+
}),
1079+
},
1080+
],
1081+
});
1082+
1083+
```
1084+
10541085
### Volumes
10551086

10561087
Whereas a `BlockDeviceVolume` is an EBS volume that is created and destroyed as part of the creation and destruction of a specific instance. A `Volume` is for when you want an EBS volume separate from any particular instance. A `Volume` is an EBS block device that can be attached to, or detached from, any instance at any time. Some types of `Volume`s can also be attached to multiple instances at the same time to allow you to have shared storage between those instances.

packages/@aws-cdk/aws-ec2/lib/private/ebs-util.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ function synthesizeBlockDeviceMappings<RT, NDT>(construct: Construct, blockDevic
2424
return blockDevices.map<RT>(({ deviceName, volume, mappingEnabled }): RT => {
2525
const { virtualName, ebsDevice: ebs } = volume;
2626

27+
let finalEbs: CfnLaunchTemplate.EbsProperty | CfnInstance.EbsProperty | undefined;
28+
2729
if (ebs) {
28-
const { iops, volumeType } = ebs;
30+
31+
const { iops, volumeType, kmsKey, ...rest } = ebs;
2932

3033
if (!iops) {
3134
if (volumeType === EbsDeviceVolumeType.IO1) {
@@ -34,9 +37,25 @@ function synthesizeBlockDeviceMappings<RT, NDT>(construct: Construct, blockDevic
3437
} else if (volumeType !== EbsDeviceVolumeType.IO1) {
3538
Annotations.of(construct).addWarning('iops will be ignored without volumeType: EbsDeviceVolumeType.IO1');
3639
}
40+
41+
/**
42+
* Because the Ebs properties of the L2 Constructs do not match the Ebs properties of the Cfn Constructs,
43+
* we have to do some transformation and handle all destructed properties
44+
*/
45+
46+
finalEbs = {
47+
...rest,
48+
iops,
49+
volumeType,
50+
kmsKeyId: kmsKey?.keyArn,
51+
};
52+
53+
} else {
54+
finalEbs = undefined;
3755
}
3856

57+
3958
const noDevice = mappingEnabled === false ? noDeviceValue : undefined;
40-
return { deviceName, ebs, virtualName, noDevice } as any;
59+
return { deviceName, ebs: finalEbs, virtualName, noDevice } as any;
4160
});
4261
}

packages/@aws-cdk/aws-ec2/lib/volume.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ export interface EbsDeviceOptions extends EbsDeviceOptionsBase {
8989
* @default false
9090
*/
9191
readonly encrypted?: boolean;
92+
93+
/**
94+
* The ARN of the AWS Key Management Service (AWS KMS) CMK used for encryption.
95+
*
96+
* You have to ensure that the KMS CMK has the correct permissions to be used by the service launching the ec2 instances.
97+
*
98+
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html#ebs-encryption-requirements
99+
*
100+
* @default - If encrypted is true, the default aws/ebs KMS key will be used.
101+
*/
102+
readonly kmsKey?: IKey;
92103
}
93104

94105
/**
@@ -108,7 +119,7 @@ export interface EbsDeviceSnapshotOptions extends EbsDeviceOptionsBase {
108119
/**
109120
* Properties of an EBS block device
110121
*/
111-
export interface EbsDeviceProps extends EbsDeviceSnapshotOptions {
122+
export interface EbsDeviceProps extends EbsDeviceSnapshotOptions, EbsDeviceOptions {
112123
/**
113124
* The snapshot ID of the volume to use
114125
*

packages/@aws-cdk/aws-ec2/test/instance.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as path from 'path';
22
import { Match, Template } from '@aws-cdk/assertions';
3+
import { Key } from '@aws-cdk/aws-kms';
34
import { Asset } from '@aws-cdk/aws-s3-assets';
45
import { StringParameter } from '@aws-cdk/aws-ssm';
56
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
@@ -184,6 +185,7 @@ describe('instance', () => {
184185
describe('blockDeviceMappings', () => {
185186
test('can set blockDeviceMappings', () => {
186187
// WHEN
188+
const kmsKey = new Key(stack, 'EbsKey');
187189
new Instance(stack, 'Instance', {
188190
vpc,
189191
machineImage: new AmazonLinuxImage(),
@@ -197,6 +199,16 @@ describe('instance', () => {
197199
volumeType: EbsDeviceVolumeType.IO1,
198200
iops: 5000,
199201
}),
202+
}, {
203+
deviceName: 'ebs-cmk',
204+
mappingEnabled: true,
205+
volume: BlockDeviceVolume.ebs(15, {
206+
deleteOnTermination: true,
207+
encrypted: true,
208+
kmsKey: kmsKey,
209+
volumeType: EbsDeviceVolumeType.IO1,
210+
iops: 5000,
211+
}),
200212
}, {
201213
deviceName: 'ebs-snapshot',
202214
mappingEnabled: false,
@@ -224,6 +236,22 @@ describe('instance', () => {
224236
VolumeType: 'io1',
225237
},
226238
},
239+
{
240+
DeviceName: 'ebs-cmk',
241+
Ebs: {
242+
DeleteOnTermination: true,
243+
Encrypted: true,
244+
KmsKeyId: {
245+
'Fn::GetAtt': [
246+
'EbsKeyD3FEE551',
247+
'Arn',
248+
],
249+
},
250+
Iops: 5000,
251+
VolumeSize: 15,
252+
VolumeType: 'io1',
253+
},
254+
},
227255
{
228256
DeviceName: 'ebs-snapshot',
229257
Ebs: {

packages/@aws-cdk/aws-ec2/test/launch-template.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Role,
55
ServicePrincipal,
66
} from '@aws-cdk/aws-iam';
7+
import { Key } from '@aws-cdk/aws-kms';
78
import {
89
App,
910
Duration,
@@ -254,6 +255,7 @@ describe('LaunchTemplate', () => {
254255

255256
test('Given blockDeviceMapping', () => {
256257
// GIVEN
258+
const kmsKey = new Key(stack, 'EbsKey');
257259
const blockDevices: BlockDevice[] = [
258260
{
259261
deviceName: 'ebs',
@@ -264,6 +266,16 @@ describe('LaunchTemplate', () => {
264266
volumeType: EbsDeviceVolumeType.IO1,
265267
iops: 5000,
266268
}),
269+
}, {
270+
deviceName: 'ebs-cmk',
271+
mappingEnabled: true,
272+
volume: BlockDeviceVolume.ebs(15, {
273+
deleteOnTermination: true,
274+
encrypted: true,
275+
kmsKey: kmsKey,
276+
volumeType: EbsDeviceVolumeType.IO1,
277+
iops: 5000,
278+
}),
267279
}, {
268280
deviceName: 'ebs-snapshot',
269281
mappingEnabled: false,
@@ -297,6 +309,22 @@ describe('LaunchTemplate', () => {
297309
VolumeType: 'io1',
298310
},
299311
},
312+
{
313+
DeviceName: 'ebs-cmk',
314+
Ebs: {
315+
DeleteOnTermination: true,
316+
Encrypted: true,
317+
KmsKeyId: {
318+
'Fn::GetAtt': [
319+
'EbsKeyD3FEE551',
320+
'Arn',
321+
],
322+
},
323+
Iops: 5000,
324+
VolumeSize: 15,
325+
VolumeType: 'io1',
326+
},
327+
},
300328
{
301329
DeviceName: 'ebs-snapshot',
302330
Ebs: {

0 commit comments

Comments
 (0)