From 53b3867c507ffd01f8a0c5d85230ea3ac8a5e3f4 Mon Sep 17 00:00:00 2001 From: Ali Hamidi Date: Wed, 23 Mar 2016 10:35:47 -0700 Subject: [PATCH] allow packer to create an encrypted copy of the AMI --- builder/amazon/common/ami_config.go | 1 + builder/amazon/ebs/builder.go | 1 + builder/amazon/ebs/step_encrypted_ami.go | 114 ++++++++++++++++++ .../source/docs/builders/amazon-ebs.html.md | 4 + 4 files changed, 120 insertions(+) create mode 100644 builder/amazon/ebs/step_encrypted_ami.go diff --git a/builder/amazon/common/ami_config.go b/builder/amazon/common/ami_config.go index 078dbefaa..0d5c584c2 100644 --- a/builder/amazon/common/ami_config.go +++ b/builder/amazon/common/ami_config.go @@ -19,6 +19,7 @@ type AMIConfig struct { AMITags map[string]string `mapstructure:"tags"` AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"` AMIForceDeregister bool `mapstructure:"force_deregister"` + AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"` } func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error { diff --git a/builder/amazon/ebs/builder.go b/builder/amazon/ebs/builder.go index 638efa5a3..ff54cee12 100644 --- a/builder/amazon/ebs/builder.go +++ b/builder/amazon/ebs/builder.go @@ -162,6 +162,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe AMIName: b.config.AMIName, }, &stepCreateAMI{}, + &stepCreateEncryptedAMICopy{}, &awscommon.StepAMIRegionCopy{ AccessConfig: &b.config.AccessConfig, Regions: b.config.AMIRegions, diff --git a/builder/amazon/ebs/step_encrypted_ami.go b/builder/amazon/ebs/step_encrypted_ami.go new file mode 100644 index 000000000..a8f8b25da --- /dev/null +++ b/builder/amazon/ebs/step_encrypted_ami.go @@ -0,0 +1,114 @@ +package ebs + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/mitchellh/multistep" + awscommon "github.com/mitchellh/packer/builder/amazon/common" + "github.com/mitchellh/packer/packer" +) + +type stepCreateEncryptedAMICopy struct { + image *ec2.Image +} + +func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(Config) + ec2conn := state.Get("ec2").(*ec2.EC2) + ui := state.Get("ui").(packer.Ui) + + // Encrypt boot not set, so skip step + if !config.AMIConfig.AMIEncryptBootVolume { + return multistep.ActionContinue + } + + ui.Say("Creating Encrypted AMI Copy") + + amis := state.Get("amis").(map[string]string) + var region, id string + if amis != nil { + for region, id = range amis { + break // Only get the first + } + } + + ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id)) + + copyOpts := &ec2.CopyImageInput{ + Name: &config.AMIName, // Try to overwrite existing AMI + SourceImageId: aws.String(id), + SourceRegion: aws.String(region), + Encrypted: aws.Bool(true), + } + + copyResp, err := ec2conn.CopyImage(copyOpts) + if err != nil { + err := fmt.Errorf("Error copying AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Wait for the copy to become ready + stateChange := awscommon.StateChangeConf{ + Pending: []string{"pending"}, + Target: "available", + Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *copyResp.ImageId), + StepState: state, + } + + ui.Say("Waiting for AMI copy to become ready...") + if _, err := awscommon.WaitForState(&stateChange); err != nil { + err := fmt.Errorf("Error waiting for AMI Copy: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Remove unencrypted AMI + ui.Say("Deregistering unecrypted AMI") + deregisterOpts := &ec2.DeregisterImageInput{ImageId: aws.String(id)} + if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil { + ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err)) + return multistep.ActionHalt + } + + // Replace original AMI ID with Encrypted ID in state + amis[region] = *copyResp.ImageId + state.Put("amis", amis) + + imagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{copyResp.ImageId}}) + if err != nil { + err := fmt.Errorf("Error searching for AMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.image = imagesResp.Images[0] + + return multistep.ActionContinue +} + +func (s *stepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) { + if s.image == nil { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + if !cancelled && !halted { + return + } + + ec2conn := state.Get("ec2").(*ec2.EC2) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deregistering the AMI because cancelation or error...") + deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId} + if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil { + ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err)) + return + } +} diff --git a/website/source/docs/builders/amazon-ebs.html.md b/website/source/docs/builders/amazon-ebs.html.md index a11f8cbba..f71cb4b2b 100644 --- a/website/source/docs/builders/amazon-ebs.html.md +++ b/website/source/docs/builders/amazon-ebs.html.md @@ -140,6 +140,10 @@ builder. - `force_deregister` (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default `false`. +- `encrypt_boot` (boolean) - Instruct packer to automatically create a copy of the + AMI with an encrypted boot volume (discarding the initial unencrypted AMI in the + process). + - `iam_instance_profile` (string) - The name of an [IAM instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) to launch the EC2 instance with.