Merge pull request #4948 from hashicorp/kms_key_regions

Kms key regions
This commit is contained in:
Matthew Hooker 2017-06-01 13:30:31 -07:00 committed by GitHub
commit 1f4b532ed5
13 changed files with 184 additions and 22 deletions

View File

@ -264,9 +264,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName, Name: b.config.AMIName,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions, Regions: b.config.AMIRegions,
Name: b.config.AMIName, RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
}, },
&awscommon.StepModifyAMIAttributes{ &awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription, Description: b.config.AMIDescription,

View File

@ -22,11 +22,21 @@ type AMIConfig struct {
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"` AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"` AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
AMIKmsKeyId string `mapstructure:"kms_key_id"` AMIKmsKeyId string `mapstructure:"kms_key_id"`
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
SnapshotTags map[string]string `mapstructure:"snapshot_tags"` SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
SnapshotUsers []string `mapstructure:"snapshot_users"` SnapshotUsers []string `mapstructure:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups"` SnapshotGroups []string `mapstructure:"snapshot_groups"`
} }
func stringInSlice(s []string, searchstr string) bool {
for _, item := range s {
if item == searchstr {
return true
}
}
return false
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error { func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error var errs []error
if c.AMIName == "" { if c.AMIName == "" {
@ -54,18 +64,44 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
} }
} }
// Make sure that if we have region_kms_key_ids defined,
// the regions in ami_regions are also in region_kms_key_ids
if len(c.AMIRegionKMSKeyIDs) > 0 {
if _, ok := c.AMIRegionKMSKeyIDs[region]; !ok {
errs = append(errs, fmt.Errorf("Region %s is in ami_regions but not in region_kms_key_ids", region))
}
}
regions = append(regions, region) regions = append(regions, region)
} }
c.AMIRegions = regions c.AMIRegions = regions
} }
// Make sure that if we have region_kms_key_ids defined,
// the regions in region_kms_key_ids are also in ami_regions
if len(c.AMIRegionKMSKeyIDs) > 0 {
for kmsKeyRegion := range c.AMIRegionKMSKeyIDs {
if !stringInSlice(c.AMIRegions, kmsKeyRegion) {
errs = append(errs, fmt.Errorf("Region %s is in region_kms_key_ids but not in ami_regions", kmsKeyRegion))
}
}
}
if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume { if len(c.AMIUsers) > 0 && c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume")) errs = append(errs, fmt.Errorf("Cannot share AMI with encrypted boot volume"))
} }
if len(c.SnapshotUsers) > 0 && len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume { if len(c.SnapshotUsers) > 0 {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key")) if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
if len(c.AMIRegionKMSKeyIDs) > 0 {
for _, kmsKey := range c.AMIRegionKMSKeyIDs {
if len(kmsKey) == 0 {
errs = append(errs, fmt.Errorf("Cannot share snapshot encrypted with default KMS key"))
}
}
}
} }
if len(c.AMIName) < 3 || len(c.AMIName) > 128 { if len(c.AMIName) < 3 || len(c.AMIName) > 128 {

View File

@ -57,6 +57,67 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
} }
c.AMISkipRegionValidation = false c.AMISkipRegionValidation = false
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if err := c.Prepare(nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if err := c.Prepare(nil); err != nil {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
c.SnapshotUsers = []string{"user-foo", "user-bar"}
c.AMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
c.SnapshotUsers = []string{"foo", "bar"}
c.AMIKmsKeyId = "123-abc-456"
c.AMIEncryptBootVolume = true
c.AMIRegions = []string{"us-east-1", "us-west-1"}
c.AMIRegionKMSKeyIDs = map[string]string{
"us-east-1": "123-456-7890",
"us-west-1": "",
}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
} }
func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) { func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {

View File

@ -13,9 +13,11 @@ import (
) )
type StepAMIRegionCopy struct { type StepAMIRegionCopy struct {
AccessConfig *AccessConfig AccessConfig *AccessConfig
Regions []string Regions []string
Name string RegionKeyIds map[string]string
EncryptBootVolume bool
Name string
} }
func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction { func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
@ -33,6 +35,7 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
var lock sync.Mutex var lock sync.Mutex
var wg sync.WaitGroup var wg sync.WaitGroup
var regKeyID string
errs := new(packer.MultiError) errs := new(packer.MultiError)
for _, region := range s.Regions { for _, region := range s.Regions {
if region == *ec2conn.Config.Region { if region == *ec2conn.Config.Region {
@ -44,10 +47,13 @@ func (s *StepAMIRegionCopy) Run(state multistep.StateBag) multistep.StepAction {
wg.Add(1) wg.Add(1)
ui.Message(fmt.Sprintf("Copying to: %s", region)) ui.Message(fmt.Sprintf("Copying to: %s", region))
if s.EncryptBootVolume {
regKeyID = s.RegionKeyIds[region]
}
go func(region string) { go func(region string) {
defer wg.Done() defer wg.Done()
id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region) id, snapshotIds, err := amiRegionCopy(state, s.AccessConfig, s.Name, ami, region, *ec2conn.Config.Region, regKeyID)
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
amis[region] = id amis[region] = id
@ -80,8 +86,9 @@ func (s *StepAMIRegionCopy) Cleanup(state multistep.StateBag) {
// amiRegionCopy does a copy for the given AMI to the target region and // amiRegionCopy does a copy for the given AMI to the target region and
// returns the resulting ID and snapshot IDs, or error. // returns the resulting ID and snapshot IDs, or error.
func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string, func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string, imageId string,
target string, source string) (string, []string, error) { target string, source string, keyID string) (string, []string, error) {
snapshotIds := []string{} snapshotIds := []string{}
isEncrypted := false
// Connect to the region where the AMI will be copied to // Connect to the region where the AMI will be copied to
awsConfig, err := config.Config() awsConfig, err := config.Config()
@ -95,11 +102,16 @@ func amiRegionCopy(state multistep.StateBag, config *AccessConfig, name string,
return "", snapshotIds, err return "", snapshotIds, err
} }
regionconn := ec2.New(session) regionconn := ec2.New(session)
// if we've provided a map of key ids to regions, use those keys.
if len(keyID) > 0 {
isEncrypted = true
}
resp, err := regionconn.CopyImage(&ec2.CopyImageInput{ resp, err := regionconn.CopyImage(&ec2.CopyImageInput{
SourceRegion: &source, SourceRegion: &source,
SourceImageId: &imageId, SourceImageId: &imageId,
Name: &name, Name: &name,
Encrypted: aws.Bool(isEncrypted),
KmsKeyId: aws.String(keyID),
}) })
if err != nil { if err != nil {

View File

@ -36,7 +36,7 @@ func (s *StepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
var region, id string var region, id string
if amis != nil { if amis != nil {
for region, id = range amis { for region, id = range amis {
break // Only get the first break // There is only ever one region:ami pair in this map
} }
} }

View File

@ -198,9 +198,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName, Name: b.config.AMIName,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions, Regions: b.config.AMIRegions,
Name: b.config.AMIName, RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
}, },
&awscommon.StepModifyAMIAttributes{ &awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription, Description: b.config.AMIDescription,

View File

@ -217,9 +217,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Name: b.config.AMIName, Name: b.config.AMIName,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions, Regions: b.config.AMIRegions,
Name: b.config.AMIName, RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
}, },
&awscommon.StepModifyAMIAttributes{ &awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription, Description: b.config.AMIDescription,

View File

@ -270,9 +270,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}, },
&StepRegisterAMI{}, &StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions, Regions: b.config.AMIRegions,
Name: b.config.AMIName, RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
}, },
&awscommon.StepModifyAMIAttributes{ &awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription, Description: b.config.AMIDescription,

View File

@ -194,6 +194,15 @@ each category, the available configuration keys are alphabetized.
volumes, io1 for Provisioned IOPS (SSD) volumes, and standard for Magnetic volumes, io1 for Provisioned IOPS (SSD) volumes, and standard for Magnetic
volumes volumes
- `region_kms_key_ids` (map of strings) - a map of regions to copy the ami to,
along with the custom kms key id to use for encryption for that region.
Keys must match the regions provided in `ami_regions`. If you just want to
encrypt using a default ID, you can stick with `kms_key_id` and `ami_regions`.
If you want a region to be encrypted with that region's default key ID, you can
use an empty string `""` instead of a key id in this map. (e.g. `"us-east-1": ""`)
However, you cannot use default key IDs if you are using this in conjunction with
`snapshot_users` -- in that situation you must use custom keys.
- `root_device_name` (string) - The root device name. For example, `xvda`. - `root_device_name` (string) - The root device name. For example, `xvda`.
- `mount_path` (string) - The path where the volume will be mounted. This is - `mount_path` (string) - The path where the volume will be mounted. This is

View File

@ -195,6 +195,15 @@ builder.
preserved when booting from the AMI built with Packer. See preserved when booting from the AMI built with Packer. See
`ami_block_device_mappings`, above, for details. `ami_block_device_mappings`, above, for details.
- `region_kms_key_ids` (map of strings) - a map of regions to copy the ami to,
along with the custom kms key id to use for encryption for that region.
Keys must match the regions provided in `ami_regions`. If you just want to
encrypt using a default ID, you can stick with `kms_key_id` and `ami_regions`.
If you want a region to be encrypted with that region's default key ID, you can
use an empty string `""` instead of a key id in this map. (e.g. `"us-east-1": ""`)
However, you cannot use default key IDs if you are using this in conjunction with
`snapshot_users` -- in that situation you must use custom keys.
- `run_tags` (object of key/value strings) - Tags to apply to the instance - `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the AMI. These tags are *not* applied to the that is *launched* to create the AMI. These tags are *not* applied to the
resulting AMI unless they're duplicated in `tags`. This is a resulting AMI unless they're duplicated in `tags`. This is a

View File

@ -188,6 +188,15 @@ builder.
preserved when booting from the AMI built with packer. See preserved when booting from the AMI built with packer. See
`ami_block_device_mappings`, above, for details. `ami_block_device_mappings`, above, for details.
- `region_kms_key_ids` (map of strings) - a map of regions to copy the ami to,
along with the custom kms key id to use for encryption for that region.
Keys must match the regions provided in `ami_regions`. If you just want to
encrypt using a default ID, you can stick with `kms_key_id` and `ami_regions`.
If you want a region to be encrypted with that region's default key ID, you can
use an empty string `""` instead of a key id in this map. (e.g. `"us-east-1": ""`)
However, you cannot use default key IDs if you are using this in conjunction with
`snapshot_users` -- in that situation you must use custom keys.
- `run_tags` (object of key/value strings) - Tags to apply to the instance - `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the AMI. These tags are *not* applied to the that is *launched* to create the AMI. These tags are *not* applied to the
resulting AMI unless they're duplicated in `tags`. This is a resulting AMI unless they're duplicated in `tags`. This is a

View File

@ -115,6 +115,15 @@ builder.
profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html)
to launch the EC2 instance with. to launch the EC2 instance with.
- `region_kms_key_ids` (map of strings) - a map of regions to copy the ami to,
along with the custom kms key id to use for encryption for that region.
Keys must match the regions provided in `ami_regions`. If you just want to
encrypt using a default ID, you can stick with `kms_key_id` and `ami_regions`.
If you want a region to be encrypted with that region's default key ID, you can
use an empty string `""` instead of a key id in this map. (e.g. `"us-east-1": ""`)
However, you cannot use default key IDs if you are using this in conjunction with
`snapshot_users` -- in that situation you must use custom keys.
- `run_tags` (object of key/value strings) - Tags to apply to the instance - `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the AMI. These tags are *not* applied to the that is *launched* to create the AMI. These tags are *not* applied to the
resulting AMI unless they're duplicated in `tags`. This is a resulting AMI unless they're duplicated in `tags`. This is a

View File

@ -211,6 +211,15 @@ builder.
preserved when booting from the AMI built with Packer. See preserved when booting from the AMI built with Packer. See
`ami_block_device_mappings`, above, for details. `ami_block_device_mappings`, above, for details.
- `region_kms_key_ids` (map of strings) - a map of regions to copy the ami to,
along with the custom kms key id to use for encryption for that region.
Keys must match the regions provided in `ami_regions`. If you just want to
encrypt using a default ID, you can stick with `kms_key_id` and `ami_regions`.
If you want a region to be encrypted with that region's default key ID, you can
use an empty string `""` instead of a key id in this map. (e.g. `"us-east-1": ""`)
However, you cannot use default key IDs if you are using this in conjunction with
`snapshot_users` -- in that situation you must use custom keys.
- `run_tags` (object of key/value strings) - Tags to apply to the instance - `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the AMI. These tags are *not* applied to the that is *launched* to create the AMI. These tags are *not* applied to the
resulting AMI unless they're duplicated in `tags`. This is a resulting AMI unless they're duplicated in `tags`. This is a