diff --git a/builder/amazon/chroot/builder.go b/builder/amazon/chroot/builder.go index cf3ed4dae..fbbff11a0 100644 --- a/builder/amazon/chroot/builder.go +++ b/builder/amazon/chroot/builder.go @@ -10,11 +10,14 @@ package chroot import ( "context" "errors" + "fmt" + "regexp" "runtime" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/hcl/v2/hcldec" awscommon "github.com/hashicorp/packer/builder/amazon/common" + "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer-plugin-sdk/chroot" "github.com/hashicorp/packer/packer-plugin-sdk/common" "github.com/hashicorp/packer/packer-plugin-sdk/multistep" @@ -169,6 +172,26 @@ type Config struct { // [`dynamic_block`](/docs/configuration/from-1.5/expressions#dynamic-blocks) // will allow you to create those programatically. RootVolumeTag config.KeyValues `mapstructure:"root_volume_tag" required:"false"` + // Whether or not to encrypt the volumes that are *launched*. By default, Packer will keep + // the encryption setting to what it was in the source image when set to `false`. Setting true will + // always result in an encrypted one. + RootVolumeEncryptBoot config.Trilean `mapstructure:"root_volume_encrypt_boot" required:"false"` + // ID, alias or ARN of the KMS key to use for *launched* volumes encryption. + // + // Set this value if you select `root_volume_encrypt_boot`, but don't want to use the + // region's default KMS key. + // + // If you have a custom kms key you'd like to apply to the launch volume, + // and are only building in one region, it is more efficient to set this + // and `root_volume_encrypt_boot` to `true` and not use `encrypt_boot` and `kms_key_id`. This saves + // potentially many minutes at the end of the build by preventing Packer + // from having to copy and re-encrypt the image at the end of the build. + // + // For valid formats see *KmsKeyId* in the [AWS API docs - + // CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html). + // This field is validated by Packer, when using an alias, you will have to + // prefix `kms_key_id` with `alias/`. + RootVolumeKmsKeyId string `mapstructure:"root_volume_kms_key_id" required:"false"` // what architecture to use when registering the final AMI; valid options // are "x86_64" or "arm64". Defaults to "x86_64". Architecture string `mapstructure:"ami_architecture" required:"false"` @@ -322,6 +345,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { errs = packersdk.MultiErrorAppend( errs, errors.New("If root_device_name is specified, ami_block_device_mappings must be specified")) } + + if b.config.RootVolumeKmsKeyId != "" { + if b.config.RootVolumeEncryptBoot.False() { + errs = packer.MultiErrorAppend( + errs, errors.New("If you have set root_kms_key_id, root_encrypt_boot must also be true.")) + } else if b.config.RootVolumeEncryptBoot.True() && !validateKmsKey(b.config.RootVolumeKmsKeyId) { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("%q is not a valid KMS Key Id.", b.config.RootVolumeKmsKeyId)) + } + } + } valid := false for _, validArch := range []string{"x86_64", "arm64"} { @@ -402,11 +436,13 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) GeneratedData: generatedData, }, &StepCreateVolume{ - PollingConfig: b.config.PollingConfig, - RootVolumeType: b.config.RootVolumeType, - RootVolumeSize: b.config.RootVolumeSize, - RootVolumeTags: b.config.RootVolumeTags, - Ctx: b.config.ctx, + PollingConfig: b.config.PollingConfig, + RootVolumeType: b.config.RootVolumeType, + RootVolumeSize: b.config.RootVolumeSize, + RootVolumeTags: b.config.RootVolumeTags, + RootVolumeEncryptBoot: b.config.RootVolumeEncryptBoot, + RootVolumeKmsKeyId: b.config.RootVolumeKmsKeyId, + Ctx: b.config.ctx, }, &StepAttachVolume{ PollingConfig: b.config.PollingConfig, @@ -501,3 +537,22 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) return artifact, nil } + +func validateKmsKey(kmsKey string) (valid bool) { + kmsKeyIdPattern := `[a-f0-9-]+$` + aliasPattern := `alias/[a-zA-Z0-9:/_-]+$` + kmsArnStartPattern := `^arn:aws(-us-gov)?:kms:([a-z]{2}-(gov-)?[a-z]+-\d{1})?:(\d{12}):` + if regexp.MustCompile(fmt.Sprintf("^%s", kmsKeyIdPattern)).MatchString(kmsKey) { + return true + } + if regexp.MustCompile(fmt.Sprintf("^%s", aliasPattern)).MatchString(kmsKey) { + return true + } + if regexp.MustCompile(fmt.Sprintf("%skey/%s", kmsArnStartPattern, kmsKeyIdPattern)).MatchString(kmsKey) { + return true + } + if regexp.MustCompile(fmt.Sprintf("%s%s", kmsArnStartPattern, aliasPattern)).MatchString(kmsKey) { + return true + } + return false +} diff --git a/builder/amazon/chroot/builder.hcl2spec.go b/builder/amazon/chroot/builder.hcl2spec.go index 572ec8a2d..5b2ba32d5 100644 --- a/builder/amazon/chroot/builder.hcl2spec.go +++ b/builder/amazon/chroot/builder.hcl2spec.go @@ -76,6 +76,8 @@ type FlatConfig struct { SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter" hcl:"source_ami_filter"` RootVolumeTags map[string]string `mapstructure:"root_volume_tags" required:"false" cty:"root_volume_tags" hcl:"root_volume_tags"` RootVolumeTag []config.FlatKeyValue `mapstructure:"root_volume_tag" required:"false" cty:"root_volume_tag" hcl:"root_volume_tag"` + RootVolumeEncryptBoot *bool `mapstructure:"root_volume_encrypt_boot" required:"false" cty:"root_volume_encrypt_boot" hcl:"root_volume_encrypt_boot"` + RootVolumeKmsKeyId *string `mapstructure:"root_volume_kms_key_id" required:"false" cty:"root_volume_kms_key_id" hcl:"root_volume_kms_key_id"` Architecture *string `mapstructure:"ami_architecture" required:"false" cty:"ami_architecture" hcl:"ami_architecture"` } @@ -155,7 +157,13 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false}, "source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())}, "root_volume_tags": &hcldec.AttrSpec{Name: "root_volume_tags", Type: cty.Map(cty.String), Required: false}, +<<<<<<< HEAD "root_volume_tag": &hcldec.BlockListSpec{TypeName: "root_volume_tag", Nested: hcldec.ObjectSpec((*config.FlatKeyValue)(nil).HCL2Spec())}, +======= + "root_volume_tag": &hcldec.BlockListSpec{TypeName: "root_volume_tag", Nested: hcldec.ObjectSpec((*hcl2template.FlatKeyValue)(nil).HCL2Spec())}, + "root_volume_encrypt_boot": &hcldec.AttrSpec{Name: "root_volume_encrypt_boot", Type: cty.Bool, Required: false}, + "root_volume_kms_key_id": &hcldec.AttrSpec{Name: "root_volume_kms_key_id", Type: cty.String, Required: false}, +>>>>>>> d2717fdcb (adding support for root volume encryption for amazon-chroot) "ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false}, } return s diff --git a/builder/amazon/chroot/step_create_volume.go b/builder/amazon/chroot/step_create_volume.go index 2b4a2a061..0d68bf551 100644 --- a/builder/amazon/chroot/step_create_volume.go +++ b/builder/amazon/chroot/step_create_volume.go @@ -9,9 +9,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" awscommon "github.com/hashicorp/packer/builder/amazon/common" + "github.com/hashicorp/packer/helper/config" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer" "github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate" + "github.com/hashicorp/packer/template/interpolate" ) // StepCreateVolume creates a new volume from the snapshot of the root @@ -20,12 +23,14 @@ import ( // Produces: // volume_id string - The ID of the created volume type StepCreateVolume struct { - PollingConfig *awscommon.AWSPollingConfig - volumeId string - RootVolumeSize int64 - RootVolumeType string - RootVolumeTags map[string]string - Ctx interpolate.Context + PollingConfig *awscommon.AWSPollingConfig + volumeId string + RootVolumeSize int64 + RootVolumeType string + RootVolumeTags map[string]string + RootVolumeEncryptBoot config.Trilean + RootVolumeKmsKeyId string + Ctx interpolate.Context } func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -148,11 +153,21 @@ func (s *StepCreateVolume) buildCreateVolumeInput(az string, rootDevice *ec2.Blo SnapshotId: rootDevice.Ebs.SnapshotId, VolumeType: rootDevice.Ebs.VolumeType, Iops: rootDevice.Ebs.Iops, + Encrypted: rootDevice.Ebs.Encrypted, + KmsKeyId: rootDevice.Ebs.KmsKeyId, } if s.RootVolumeSize > *rootDevice.Ebs.VolumeSize { createVolumeInput.Size = aws.Int64(s.RootVolumeSize) } + if s.RootVolumeEncryptBoot.True() { + createVolumeInput.Encrypted = aws.Bool(true) + } + + if s.RootVolumeKmsKeyId != "" { + createVolumeInput.KmsKeyId = aws.String(s.RootVolumeKmsKeyId) + } + if s.RootVolumeType == "" || s.RootVolumeType == *rootDevice.Ebs.VolumeType { return createVolumeInput, nil } diff --git a/builder/amazon/chroot/step_create_volume_test.go b/builder/amazon/chroot/step_create_volume_test.go index a522d5026..177cc07b1 100644 --- a/builder/amazon/chroot/step_create_volume_test.go +++ b/builder/amazon/chroot/step_create_volume_test.go @@ -5,6 +5,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + confighelper "github.com/hashicorp/packer/helper/config" "github.com/stretchr/testify/assert" ) @@ -14,6 +15,7 @@ func buildTestRootDevice() *ec2.BlockDeviceMapping { VolumeSize: aws.Int64(10), SnapshotId: aws.String("snap-1234"), VolumeType: aws.String("gp2"), + Encrypted: aws.Bool(false), }, } } @@ -72,3 +74,24 @@ func TestCreateVolume_gp2_to_io1(t *testing.T) { _, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) assert.Error(t, err) } + +func TestCreateVolume_Encrypted(t *testing.T) { + stepCreateVolume := StepCreateVolume{RootVolumeEncryptBoot: confighelper.TrileanFromBool(true)} + testRootDevice := buildTestRootDevice() + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + // Ensure that the new value is equal to the the value passed in + assert.Equal(t, confighelper.TrileanFromBool(*ret.Encrypted), stepCreateVolume.RootVolumeEncryptBoot) +} + +func TestCreateVolume_Custom_KMS_Key_Encrypted(t *testing.T) { + stepCreateVolume := StepCreateVolume{ + RootVolumeEncryptBoot: confighelper.TrileanFromBool(true), + RootVolumeKmsKeyId: "alias/1234", + } + testRootDevice := buildTestRootDevice() + ret, err := stepCreateVolume.buildCreateVolumeInput("test-az", testRootDevice) + assert.NoError(t, err) + // Ensure that the new value is equal to the value passed in + assert.Equal(t, *ret.KmsKeyId, stepCreateVolume.RootVolumeKmsKeyId) +} diff --git a/website/content/partials/builder/amazon/chroot/Config-not-required.mdx b/website/content/partials/builder/amazon/chroot/Config-not-required.mdx index c5dcc623b..841490723 100644 --- a/website/content/partials/builder/amazon/chroot/Config-not-required.mdx +++ b/website/content/partials/builder/amazon/chroot/Config-not-required.mdx @@ -130,5 +130,25 @@ [`dynamic_block`](/docs/configuration/from-1.5/expressions#dynamic-blocks) will allow you to create those programatically. +- `root_volume_encrypt_boot` (boolean) - Whether or not to encrypt the volumes that are *launched*. By default, Packer will keep + the encryption setting to what it was in the source image when set to `false`. Setting true will + always result in an encrypted one. + +- `root_volume_kms_key_id` (string) - ID, alias or ARN of the KMS key to use for *launched* volumes encryption. + + Set this value if you select `root_volume_encrypt_boot`, but don't want to use the + region's default KMS key. + + If you have a custom kms key you'd like to apply to the launch volume, + and are only building in one region, it is more efficient to set this + and `root_volume_encrypt_boot` to `true` and not use `encrypt_boot` and `kms_key_id`. This saves + potentially many minutes at the end of the build by preventing Packer + from having to copy and re-encrypt the image at the end of the build. + + For valid formats see *KmsKeyId* in the [AWS API docs - + CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html). + This field is validated by Packer, when using an alias, you will have to + prefix `kms_key_id` with `alias/`. + - `ami_architecture` (string) - what architecture to use when registering the final AMI; valid options are "x86_64" or "arm64". Defaults to "x86_64".