Encrypted Amazon EC2 boot volumes with Packer and Ansible
Let's work together!
Are you hiring for remote engineering leadership positions? I help teams thrive through iterative improvement. Here's my resume. Let's talk.
I'm also available for DevOps project work. Are you fighting fires instead of serving your customers? Hire me to identify the roadblocks and make improvements so you can get back to focusing on your customers.
At the end of 2015 Amazon added support for encrypted EBS boot volumes. EBS storage volumes had offered optional encryption for some time before that. Now it’s possible to encrypt an AMI and bring up EC2 instances with fully encrypted starting volumes.
I set out recently to use encrypted EBS boot volumes for a HIPAA compliant project at ReactiveOps. It’s very easy to convert an existing AMI with an unencrypted boot volume to use encryption. I hit a few snags though building encryption into my automated AMI generation workflows using Packer and Ansible tooling.
Encrypt an existing AMI boot volume
Once you have an AMI with an encrypted boot volume any EC2 instance you launch using that AMI will have its boot volume encrypted. There’s nothing extra to do at instance launch time.
If you already have an AMI with an unencrypted boot volume it’s easy to encrypt it. This feature piggybacks on the ability to encrypt EBS volume snapshots while copying them. You can enable encryption for an AMI by copying it using the CLI, API, or the Amazon web panel and enabling encryption.
Amazon’s announcement post contains detailed instructions for copying an AMI using either the CLI or the web panel. After you make the copy you will have a new and separate AMI. Any EC2 instance you start from this new AMI will have an encrypted boot volume. There’s nothing special to do at instance launch.
The first hitch I ran into involved AMI’s from the AWS Marketplace. For most projects I start with a base operating system AMI maintained by the OS’s official backing organization. Canonical publishes Ubuntu images. CentOS.org publishes CentOS images.
Unfortunately Amazon doesn’t allow AMI’s with a product code to be copied. All marketplace images have a product code. This makes it impossible to encrypt a marketplace sourced AMI by copying it into your account. The alternative is to stand up an EC2 instance from the marketplace AMI and create an AMI from the instance. The resulting AMI copy can then itself be copied and encrypted. That’s a lot of copying.
Side note: Ubuntu publishes a “Cloud Image” library of their official AMI’s. These AMI’s are separate from the AWS Marketplace. They can be copied with encryption freely unlike the marketplace versions.
Packer amazon-ebs encryption is not possible
I tend to use Packer’s amazon-ebs to build AMI’s. I had hopes for a Packer native option to produce an encrypted boot volume from a marketplace AMI. Though you can set an encryption field on volumes using the
ami_block_device_mappings this doesn’t produce an encrypted EBS snapshot for the boot volume. It does not appear there is a way to do this natively with Packer. I had to resort to following up Packer AMI builds with a separate process to copy the resulting unencrypted AMI to an encrypted format.
Creating encrypted AMI’s with Ansible
The following Ansible playbook copies an unencrypted AMI and encrypts it. It tags the resulting AMI with
Encrypted='true'. Ansible 2.0 or greater is required.
Ansible has an
ec2_ami_copy module. Due to limitations in Boto though this module does not yet support enabling encryption on copy. There are pending PR’s for bringing this feature to the module. Until those are released, this code shells out to the AWS CLI to create the AMI copy. You’ll need to configure the AWS CLI with appropriate credentials on your machine to use this playbook.
--- - name: Create an encrypted copy of a given AMI hosts: localhost connection: local gather_facts: False vars: aws_region: "us-east-1" unencrypted_ami_id: "ami-XXXXXXXX" tasks: - name: Find the latest AMI by tags ec2_ami_find: owner: self ami_id: "" no_result_action: fail region: "" sort: name sort_order: descending sort_end: 1 register: latest_ami - set_fact: unencrypted_ami_name="" - name: Check if encrypted copy of AMI already exists ec2_ami_find: owner: self name: "" ami_tags: Encrypted: "true" no_result_action: success region: "" register: latest_ami_encrypted # Proceed with encrypting only if no encrypted copy already exists - block: - name: Encrypt AMI by copying it shell: "aws ec2 copy-image --source-region '' --region '' --encrypted --source-image-id '' --name ''" register: ami_copy - set_fact: ami_copy_json="" - set_fact: encrypted_ami_id="" # Retry until ami is available for tagging - name: Confirm encrypted AMI id. ec2_ami_find: owner: self ami_id: "" no_result_action: success region: "" state: pending register: confirm_ami_encrypted until: confirm_ami_encrypted.results|length > 0 retries: 4 delay: 15 - name: Tag encrypted AMI ec2_tag: region: "" resource: "" state: present tags: Name: "" Encrypted: "true" with_items: confirm_ami_encrypted.results when: latest_ami.results|length == 1 and latest_ami_encrypted.results|length == 0
March 18, 2016