aws: allow to pass nil bool as encryption parameter

to retain current encryption settings

this changes the fields :
* ami_config.encrypt_boot
* block_device.encrypted

This also removes StepCreateEncryptedAMICopy as this step is now done in StepAMIRegionCopy
This commit is contained in:
Adrien Delorme 2018-11-26 11:33:44 +01:00
parent ce8532e94b
commit f03cbd8a10
16 changed files with 70 additions and 250 deletions

View File

@ -273,12 +273,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,

View File

@ -23,7 +23,7 @@ type AMIConfig struct {
AMISriovNetSupport bool `mapstructure:"sriov_support"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot"`
AMIKmsKeyId string `mapstructure:"kms_key_id"`
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
@ -59,7 +59,7 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
errs = append(errs, c.prepareRegions(accessConfig)...)
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume != nil && *c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
}
@ -81,7 +81,7 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
}
if len(c.SnapshotUsers) > 0 {
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume != nil && *c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
if len(c.AMIRegionKMSKeyIDs) > 0 {

View File

@ -138,7 +138,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
c.SnapshotUsers = []string{"foo", "bar"}
c.AMIKmsKeyId = "123-abc-456"
c.AMIEncryptBootVolume = true
c.AMIEncryptBootVolume = &[]bool{true}[0]
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
@ -161,7 +161,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
c := testAMIConfig()
c.AMIUsers = []string{"testAccountID"}
c.AMIEncryptBootVolume = true
c.AMIEncryptBootVolume = &[]bool{true}[0]
accessConf := testAccessConfig()

View File

@ -13,7 +13,7 @@ import (
type BlockDevice struct {
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
DeviceName string `mapstructure:"device_name"`
Encrypted bool `mapstructure:"encrypted"`
Encrypted *bool `mapstructure:"encrypted"`
IOPS int64 `mapstructure:"iops"`
NoDevice bool `mapstructure:"no_device"`
SnapshotId string `mapstructure:"snapshot_id"`
@ -71,9 +71,8 @@ func buildBlockDevices(b []BlockDevice) []*ec2.BlockDeviceMapping {
// You cannot specify Encrypted if you specify a Snapshot ID
if blockDevice.SnapshotId != "" {
ebsBlockDevice.SnapshotId = aws.String(blockDevice.SnapshotId)
} else if blockDevice.Encrypted {
ebsBlockDevice.Encrypted = aws.Bool(blockDevice.Encrypted)
}
ebsBlockDevice.Encrypted = blockDevice.Encrypted
if blockDevice.KmsKeyId != "" {
ebsBlockDevice.KmsKeyId = aws.String(blockDevice.KmsKeyId)
@ -93,7 +92,7 @@ func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
"for every device in the block device mapping.")
}
// Warn that encrypted must be true when setting kms_key_id
if b.KmsKeyId != "" && b.Encrypted == false {
if b.KmsKeyId != "" && b.Encrypted != nil && *b.Encrypted == false {
return fmt.Errorf("The device %v, must also have `encrypted: "+
"true` when setting a kms_key_id.", b.DeviceName)
}

View File

@ -71,7 +71,7 @@ func TestBlockDevice(t *testing.T) {
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: true,
Encrypted: aws.Bool(true),
},
Result: &ec2.BlockDeviceMapping{
@ -90,7 +90,7 @@ func TestBlockDevice(t *testing.T) {
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnTermination: true,
Encrypted: true,
Encrypted: aws.Bool(true),
KmsKeyId: "2Fa48a521f-3aff-4b34-a159-376ac5d37812",
},

View File

@ -16,7 +16,7 @@ type StepAMIRegionCopy struct {
AccessConfig *AccessConfig
Regions []string
RegionKeyIds map[string]string
EncryptBootVolume bool
EncryptBootVolume *bool // nil means preserve
Name string
}
@ -27,6 +27,13 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
snapshots := state.Get("snapshots").(map[string][]string)
ami := amis[*ec2conn.Config.Region]
if s.EncryptBootVolume != nil {
// encrypt_boot was set, we now have to copy the temporary
// AMI with required encryption setting.
// temp image was created by stepCreateAMI.
s.Regions = append(s.Regions, *ec2conn.Config.Region)
}
if len(s.Regions) == 0 {
return multistep.ActionContinue
}
@ -40,7 +47,7 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
wg.Add(len(s.Regions))
for _, region := range s.Regions {
if region == *ec2conn.Config.Region {
if region == *ec2conn.Config.Region && s.EncryptBootVolume == nil {
ui.Message(fmt.Sprintf(
"Avoiding copying AMI to duplicate region %s", region))
continue
@ -48,13 +55,13 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
ui.Message(fmt.Sprintf("Copying to: %s", region))
if s.EncryptBootVolume {
if s.EncryptBootVolume != nil && *s.EncryptBootVolume {
regKeyID = s.RegionKeyIds[region]
}
go func(region string) {
defer wg.Done()
id, snapshotIds, err := amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID)
id, snapshotIds, err := amiRegionCopy(ctx, state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID, s.EncryptBootVolume)
lock.Lock()
defer lock.Unlock()
amis[region] = id
@ -86,20 +93,16 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
// amiRegionCopy does a copy for the given AMI to the target region and
// returns the resulting ID and snapshot IDs, or error.
func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name string, imageId string,
target string, source string, keyID string) (string, []string, error) {
func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *AccessConfig, name, imageId,
target, source, keyId string, encrypt *bool) (string, []string, error) {
snapshotIds := []string{}
isEncrypted := false
// Connect to the region where the AMI will be copied to
session, err := config.Session()
if err != nil {
return "", snapshotIds, err
}
// if we've provided a map of key ids to regions, use those keys.
if len(keyID) > 0 {
isEncrypted = true
}
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(target),
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
@ -109,8 +112,8 @@ func amiRegionCopy(ctx context.Context, state multistep.StateBag, config *Access
SourceRegion: &source,
SourceImageId: &imageId,
Name: &name,
Encrypted: aws.Bool(isEncrypted),
KmsKeyId: aws.String(keyID),
Encrypted: encrypt,
KmsKeyId: aws.String(keyId),
})
if err != nil {

View File

@ -27,7 +27,7 @@ func (s *StepDeregisterAMI) Run(_ context.Context, state multistep.StateBag) mul
ui := state.Get("ui").(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
// Add the session region to list of regions will will deregister AMIs in
// Add the session region to list of regions will deregister AMIs in
regions := append(s.Regions, *ec2conn.Config.Region)
for _, region := range regions {

View File

@ -1,178 +0,0 @@
package common
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepCreateEncryptedAMICopy struct {
image *ec2.Image
KeyID string
EncryptBootVolume bool
Name string
AMIMappings []BlockDevice
ToDelete []*string
}
func (s *StepCreateEncryptedAMICopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
kmsKeyId := s.KeyID
// Encrypt boot not set, so skip step
if !s.EncryptBootVolume {
if kmsKeyId != "" {
log.Printf("Ignoring KMS Key ID: %s, encrypted=false", kmsKeyId)
}
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 // There is only ever one region:ami pair in this map
}
}
ui.Say(fmt.Sprintf("Copying AMI: %s(%s)", region, id))
if kmsKeyId != "" {
ui.Say(fmt.Sprintf("Encrypting with KMS Key ID: %s", kmsKeyId))
}
copyOpts := &ec2.CopyImageInput{
Name: &s.Name, // Try to overwrite existing AMI
SourceImageId: aws.String(id),
SourceRegion: aws.String(region),
Encrypted: aws.Bool(true),
KmsKeyId: aws.String(kmsKeyId),
}
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
ui.Say("Waiting for AMI copy to become ready...")
if err := WaitUntilAMIAvailable(ctx, ec2conn, *copyResp.ImageId); err != nil {
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Get the encrypted AMI image, we need the new snapshot id's
encImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.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
}
encImage := encImagesResp.Images[0]
var encSnapshots []string
for _, blockDevice := range encImage.BlockDeviceMappings {
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
encSnapshots = append(encSnapshots, *blockDevice.Ebs.SnapshotId)
}
}
// Get the unencrypted AMI image
unencImagesResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{ImageIds: []*string{aws.String(id)}})
if err != nil {
err := fmt.Errorf("Error searching for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
unencImage := unencImagesResp.Images[0]
// Remove unencrypted AMI
ui.Say("Deregistering unencrypted 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
}
// Figure out which unencrypted snapshot(s) to delete
snapshots := state.Get("snapshots").(map[string][]string)
OuterLoop:
for _, blockDevice := range unencImage.BlockDeviceMappings {
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
// If this packer run didn't create it, then don't delete it
for _, origDevice := range s.AMIMappings {
if origDevice.SnapshotId == *blockDevice.Ebs.SnapshotId {
ui.Message(fmt.Sprintf("Keeping Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
continue OuterLoop
}
}
s.ToDelete = append(s.ToDelete, aws.String(*blockDevice.Ebs.SnapshotId))
}
}
// Replace original AMI ID with Encrypted ID in state
amis[region] = *copyResp.ImageId
snapshots[region] = encSnapshots
state.Put("amis", amis)
state.Put("snapshots", snapshots)
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 cancellation 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
}
ui.Say("Deleting unencrypted snapshots")
for _, snap := range s.ToDelete {
ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *snap))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: snap,
}
if _, err := ec2conn.DeleteSnapshot(deleteSnapOpts); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, may still be around: %s", err))
return
}
}
}

View File

@ -223,12 +223,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Regions: b.config.AMIRegions,
},
&stepCreateAMI{},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,

View File

@ -24,13 +24,13 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
// Create the image
amiName := config.AMIName
if config.AMIEncryptBootVolume {
// to avoid having a temporary unencrypted
// image named config.AMIName
if config.AMIEncryptBootVolume != nil {
// encrypt_boot was set, so we will create a temporary image
// and then create a copy of it with the correct encrypt_boot
amiName = random.AlphaNum(7)
}
ui.Say(fmt.Sprintf("Creating unencrypted AMI %s from instance %s", amiName, *instance.InstanceId))
ui.Say(fmt.Sprintf("Creating AMI %s from instance %s", amiName, *instance.InstanceId))
createOpts := &ec2.CreateImageInput{
InstanceId: instance.InstanceId,
Name: &amiName,
@ -94,17 +94,19 @@ func (s *stepCreateAMI) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}
config := state.Get("config").(*Config)
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
encryptBootSet := config.AMIEncryptBootVolume != nil
if !cancelled && !halted && !encryptBootSet {
return
}
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the AMI because cancellation or error...")
ui.Say("Deregistering the AMI because cancellation, error or it was temporary (encrypt_boot was set)...")
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))

View File

@ -249,11 +249,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,

View File

@ -141,11 +141,10 @@ each category, the available configuration keys are alphabetized.
documentation on enabling enhanced
networking](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html#enabling_enhanced_networking).
- `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). Packer will always run this operation,
even if the base AMI has an encrypted boot volume to start with. Default
`false`.
- `encrypt_boot` (boolean) - Whether or not to encrypt the resulting AMI when
copying a provisioned instance to an AMI. By default, Packer will keep the
encryption setting to what it was in the source image. Setting `false` will
result in an unencrypted image, and `true` will result in an encrypted one.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
AMI if one with the same name already exists. Default `false`.
@ -187,6 +186,9 @@ each category, the available configuration keys are alphabetized.
device mapping.
- `encrypted` (boolean) - Indicates whether or not to encrypt the volume.
By default, Packer will keep the encryption setting to what it was in
the source image. Setting `false` will result in an unencrypted device,
and `true` will result in an encrypted one.
- `kms_key_id` (string) - The ARN for the KMS encryption key. When
specifying `kms_key_id`, `encrypted` needs to be set to `true`. For

View File

@ -91,7 +91,10 @@ builder.
example, `/dev/sdh` or `xvdh`). Required for every device in the block
device mapping.
- `encrypted` (boolean) - Indicates whether to encrypt the volume or not
- `encrypted` (boolean) - Indicates whether or not to encrypt the volume.
By default, Packer will keep the encryption setting to what it was in
the source image. Setting `false` will result in an unencrypted device,
and `true` will result in an encrypted one.
- `iops` (number) - The number of I/O operations per second (IOPS) that
the volume supports. See the documentation on
@ -220,18 +223,16 @@ builder.
Attempting to do so will cause an error.
!> **Warning!** Additional costs may be incurred by enabling T2
Unlimited - even for instances that would usually qualify for the [AWS Free
Tier](https://aws.amazon.com/free/).
Unlimited - even for instances that would usually qualify for the
[AWS Free Tier](https://aws.amazon.com/free/).
- `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). Packer will always run this operation,
even if the base AMI has an encrypted boot volume to start with. Default
`false`.
- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots
associated with AMIs, which have been deregistered by `force_deregister`.
Default `false`.
- `encrypt_boot` (boolean) - Whether or not to encrypt the resulting AMI when
copying a provisioned instance to an AMI. By default, Packer will keep the
encryption setting to what it was in the source image. Setting `false` will
result in an unencrypted image, and `true` will result in an encrypted one.
- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots associated with
AMIs, which have been deregistered by `force_deregister`. Default `false`.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
AMI if one with the same name already exists. Default `false`.

View File

@ -81,6 +81,9 @@ builder.
device mapping.
- `encrypted` (boolean) - Indicates whether or not to encrypt the volume.
By default, Packer will keep the encryption setting to what it was in
the source image. Setting `false` will result in an unencrypted device,
and `true` will result in an encrypted one.
- `iops` (number) - The number of I/O operations per second (IOPS) that
the volume supports. See the documentation on
@ -212,11 +215,10 @@ builder.
Unlimited - even for instances that would usually qualify for the [AWS Free
Tier](https://aws.amazon.com/free/).
- `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). Packer will always run this operation,
even if the base AMI has an encrypted boot volume to start with. Default
`false`.
- `encrypt_boot` (boolean) - Whether or not to encrypt the resulting AMI when
copying a provisioned instance to an AMI. By default, Packer will keep the
encryption setting to what it was in the source image. Setting `false` will
result in an unencrypted image, and `true` will result in an encrypted one.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
AMI if one with the same name already exists. Default `false`.

View File

@ -69,7 +69,10 @@ builder.
- `delete_on_termination` (boolean) - Indicates whether the EBS volume is
deleted on instance termination.
- `encrypted` (boolean) - Indicates whether to encrypt the volume or not
- `encrypted` (boolean) - Indicates whether or not to encrypt the volume.
By default, Packer will keep the encryption setting to what it was in
the source image. Setting `false` will result in an unencrypted device,
and `true` will result in an encrypted one.
- `kms_key_id` (string) - The ARN for the KMS encryption key. When
specifying `kms_key_id`, `encrypted` needs to be set to `true`. For

View File

@ -110,7 +110,10 @@ builder.
example, `/dev/sdh` or `xvdh`). Required for every device in the block
device mapping.
- `encrypted` (boolean) - Indicates whether to encrypt the volume or not
- `encrypted` (boolean) - Indicates whether or not to encrypt the volume.
By default, Packer will keep the encryption setting to what it was in
the source image. Setting `false` will result in an unencrypted device,
and `true` will result in an encrypted one.
- `iops` (number) - The number of I/O operations per second (IOPS) that
the volume supports. See the documentation on