adding support for root volume encryption for amazon-chroot

This commit is contained in:
Dany Garcia 2020-11-10 12:27:29 -08:00 committed by Megan Marsh
parent 72c1912b60
commit 504c3807f7
5 changed files with 132 additions and 11 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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".