From 8e24c5e795c77c8efab913402af330d592fb2833 Mon Sep 17 00:00:00 2001 From: Veli-Matti Lintu Date: Thu, 15 Mar 2018 17:46:33 +0200 Subject: [PATCH 1/2] Add support for encrypted AMIs that can be used to launch instances with encrypted boot volume. --- bootstrapvz/providers/ec2/README.rst | 24 +++++++++++++++++++ bootstrapvz/providers/ec2/__init__.py | 8 +++++++ bootstrapvz/providers/ec2/ebsvolume.py | 20 +++++++++++----- bootstrapvz/providers/ec2/manifest-schema.yml | 2 ++ bootstrapvz/providers/ec2/tasks/ebs.py | 7 +++++- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/bootstrapvz/providers/ec2/README.rst b/bootstrapvz/providers/ec2/README.rst index 3a6d797..1bd2c70 100644 --- a/bootstrapvz/providers/ec2/README.rst +++ b/bootstrapvz/providers/ec2/README.rst @@ -146,6 +146,30 @@ Example: name: ec2 amzn-driver-version: 1.5.0 +Encrypted volumes +~~~~~~~~~~~~~~~~~ + +Encrypted AMIs that can be used to launch instances with encrypted boot volume +are supported. Defining encryption key is optional and EC2 uses default +encryption key if one is not set. Encryption works only with EBS volumes. + +- ``encrypted:``: Default: False + Valid values: ``True``, ``False`` + ``optional`` +- ``kms_key_id:``: Default: EC2 default EBS encryption key + Valid values: arn of the KMS key + ``optional`` + +Example: + +.. code-block:: yaml + + --- + provider: + name: ec2 + encrypted: True + kms_key_id: arn:aws:kms:us-east-1:1234567890:key/00000000-0000-0000-0000-000000000000 + Image ~~~~~ diff --git a/bootstrapvz/providers/ec2/__init__.py b/bootstrapvz/providers/ec2/__init__.py index 9929987..8431aa4 100644 --- a/bootstrapvz/providers/ec2/__init__.py +++ b/bootstrapvz/providers/ec2/__init__.py @@ -25,6 +25,8 @@ def validate_manifest(data, validator, error): bootloader = data['system']['bootloader'] virtualization = data['provider']['virtualization'] + encrypted = data['provider'].get('encrypted', False) + kms_key_id = data['provider'].get('kms_key_id') backing = data['volume']['backing'] partition_type = data['volume']['partitions']['type'] enhanced_networking = data['provider']['enhanced_networking'] if 'enhanced_networking' in data['provider'] else None @@ -38,6 +40,12 @@ def validate_manifest(data, validator, error): if backing == 's3' and partition_type != 'none': error('S3 backed AMIs currently only work with unpartitioned volumes', ['system', 'bootloader']) + if backing != 'ebs' and encrypted: + error('Encryption is supported only on EBS volumes') + + if encrypted is False and kms_key_id is not None: + error('KMS Key Id can be set only when encryption is enabled') + if enhanced_networking == 'simple' and virtualization != 'hvm': error('Enhanced networking only works with HVM virtualization', ['provider', 'virtualization']) diff --git a/bootstrapvz/providers/ec2/ebsvolume.py b/bootstrapvz/providers/ec2/ebsvolume.py index 33e60e2..a4b4332 100644 --- a/bootstrapvz/providers/ec2/ebsvolume.py +++ b/bootstrapvz/providers/ec2/ebsvolume.py @@ -4,18 +4,26 @@ from bootstrapvz.base.fs.exceptions import VolumeError class EBSVolume(Volume): - def create(self, conn, zone, tags=[]): - self.fsm.create(connection=conn, zone=zone, tags=tags) + def create(self, conn, zone, tags=[], encrypted=False, kms_key_id=None): + self.fsm.create(connection=conn, zone=zone, tags=tags, encrypted=encrypted, kms_key_id=kms_key_id) def _before_create(self, e): self.conn = e.connection zone = e.zone tags = e.tags size = self.size.bytes.get_qty_in('GiB') - self.volume = self.conn.create_volume(Size=size, - AvailabilityZone=zone, - VolumeType='gp2', - TagSpecifications=[{'ResourceType': 'volume', 'Tags': tags}]) + + params = dict(Size=size, + AvailabilityZone=zone, + VolumeType='gp2', + TagSpecifications=[{'ResourceType': 'volume', 'Tags': tags}], + Encrypted=e.encrypted) + + if e.encrypted and e.kms_key_id: + params['KmsKeyId'] = e.kms_key_id + + self.volume = self.conn.create_volume(**params) + self.vol_id = self.volume['VolumeId'] waiter = self.conn.get_waiter('volume_available') waiter.wait(VolumeIds=[self.vol_id], diff --git a/bootstrapvz/providers/ec2/manifest-schema.yml b/bootstrapvz/providers/ec2/manifest-schema.yml index 2d9b217..c8a963b 100644 --- a/bootstrapvz/providers/ec2/manifest-schema.yml +++ b/bootstrapvz/providers/ec2/manifest-schema.yml @@ -27,6 +27,8 @@ properties: amzn-driver-version: type: string pattern: "^([0-9]+\\.?){3}$" + encrypted: { type: boolean } + kms_key_id: { type: string } required: [description, virtualization] system: type: object diff --git a/bootstrapvz/providers/ec2/tasks/ebs.py b/bootstrapvz/providers/ec2/tasks/ebs.py index c87a76e..b232c47 100644 --- a/bootstrapvz/providers/ec2/tasks/ebs.py +++ b/bootstrapvz/providers/ec2/tasks/ebs.py @@ -16,7 +16,12 @@ class Create(Task): formatted_tags = {k: v.format(**info.manifest_vars) for k, v in raw_tags.items()} tags = [{'Key': k, 'Value': v} for k, v in formatted_tags.items()] - info.volume.create(info._ec2['connection'], info._ec2['host']['availabilityZone'], tags) + # EBS volumes support encryption. KMS key id is optional and default key + # is used when it is not defined. + encrypted = info.manifest.data['provider'].get('encrypted', False) + kms_key_id = info.manifest.data['provider'].get('kms_key_id') + + info.volume.create(info._ec2['connection'], info._ec2['host']['availabilityZone'], tags=tags, encrypted=encrypted, kms_key_id=kms_key_id) class Attach(Task): From 2a741a81bfd580afa6fb10c67a56facbd893dddb Mon Sep 17 00:00:00 2001 From: Veli-Matti Lintu Date: Thu, 14 Jun 2018 09:38:36 +0300 Subject: [PATCH 2/2] Explicitly define None as the default value for missing kms_key_id key in manifest. None should be returned by default, but this makes it easier to see. --- bootstrapvz/providers/ec2/__init__.py | 2 +- bootstrapvz/providers/ec2/tasks/ebs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrapvz/providers/ec2/__init__.py b/bootstrapvz/providers/ec2/__init__.py index 8431aa4..0dd2856 100644 --- a/bootstrapvz/providers/ec2/__init__.py +++ b/bootstrapvz/providers/ec2/__init__.py @@ -26,7 +26,7 @@ def validate_manifest(data, validator, error): bootloader = data['system']['bootloader'] virtualization = data['provider']['virtualization'] encrypted = data['provider'].get('encrypted', False) - kms_key_id = data['provider'].get('kms_key_id') + kms_key_id = data['provider'].get('kms_key_id', None) backing = data['volume']['backing'] partition_type = data['volume']['partitions']['type'] enhanced_networking = data['provider']['enhanced_networking'] if 'enhanced_networking' in data['provider'] else None diff --git a/bootstrapvz/providers/ec2/tasks/ebs.py b/bootstrapvz/providers/ec2/tasks/ebs.py index b232c47..90239eb 100644 --- a/bootstrapvz/providers/ec2/tasks/ebs.py +++ b/bootstrapvz/providers/ec2/tasks/ebs.py @@ -19,7 +19,7 @@ class Create(Task): # EBS volumes support encryption. KMS key id is optional and default key # is used when it is not defined. encrypted = info.manifest.data['provider'].get('encrypted', False) - kms_key_id = info.manifest.data['provider'].get('kms_key_id') + kms_key_id = info.manifest.data['provider'].get('kms_key_id', None) info.volume.create(info._ec2['connection'], info._ec2['host']['availabilityZone'], tags=tags, encrypted=encrypted, kms_key_id=kms_key_id)