diff --git a/aws/data_source_aws_instance.go b/aws/data_source_aws_instance.go index 9b28684431f..3516810b26c 100644 --- a/aws/data_source_aws_instance.go +++ b/aws/data_source_aws_instance.go @@ -187,6 +187,11 @@ func dataSourceAwsInstance() *schema.Resource { Computed: true, }, + "kms_key_id": { + Type: schema.TypeString, + Computed: true, + }, + "snapshot_id": { Type: schema.TypeString, Computed: true, @@ -219,11 +224,21 @@ func dataSourceAwsInstance() *schema.Resource { Computed: true, }, + "encrypted": { + Type: schema.TypeBool, + Computed: true, + }, + "iops": { Type: schema.TypeInt, Computed: true, }, + "kms_key_id": { + Type: schema.TypeString, + Computed: true, + }, + "volume_size": { Type: schema.TypeInt, Computed: true, diff --git a/aws/data_source_aws_instance_test.go b/aws/data_source_aws_instance_test.go index b06b92e6976..9aec62f5a97 100644 --- a/aws/data_source_aws_instance_test.go +++ b/aws/data_source_aws_instance_test.go @@ -1,10 +1,9 @@ package aws import ( - "testing" - "fmt" "regexp" + "testing" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" @@ -107,6 +106,32 @@ func TestAccAWSInstanceDataSource_blockDevices(t *testing.T) { }) } +// Test to verify that ebs_block_device kms_key_id does not elicit a panic +func TestAccAWSInstanceDataSource_EbsBlockDevice_KmsKeyId(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_EbsBlockDevice_KmsKeyId, + }, + }, + }) +} + +// Test to verify that root_block_device kms_key_id does not elicit a panic +func TestAccAWSInstanceDataSource_RootBlockDevice_KmsKeyId(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_RootBlockDevice_KmsKeyId, + }, + }, + }) +} + func TestAccAWSInstanceDataSource_rootInstanceStore(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -486,6 +511,56 @@ data "aws_instance" "foo" { } ` +const testAccInstanceDataSourceConfig_EbsBlockDevice_KmsKeyId = ` +resource "aws_kms_key" "foo" { + deletion_window_in_days = 7 +} + +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-55a7ea65" + instance_type = "m3.medium" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + } + ebs_block_device { + device_name = "/dev/sdb" + encrypted = true + kms_key_id = "${aws_kms_key.foo.arn}" + volume_size = 9 + } +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + +const testAccInstanceDataSourceConfig_RootBlockDevice_KmsKeyId = ` +resource "aws_kms_key" "foo" { + deletion_window_in_days = 7 +} + +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-55a7ea65" + instance_type = "m3.medium" + + root_block_device { + encrypted = true + kms_key_id = "${aws_kms_key.foo.arn}" + volume_type = "gp2" + volume_size = 11 + } +} + +data "aws_instance" "foo" { + instance_id = "${aws_instance.foo.id}" +} +` + const testAccInstanceDataSourceConfig_rootInstanceStore = ` resource "aws_instance" "foo" { ami = "ami-44c36524" diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index b18587864ff..afda83246c6 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -336,6 +336,13 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "iops": { Type: schema.TypeInt, Optional: true, @@ -432,6 +439,20 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, }, + "encrypted": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "iops": { Type: schema.TypeInt, Optional: true, @@ -1328,6 +1349,12 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[st if vol.Iops != nil { bd["iops"] = *vol.Iops } + if vol.Encrypted != nil { + bd["encrypted"] = *vol.Encrypted + } + if vol.KmsKeyId != nil { + bd["kms_key_id"] = *vol.KmsKeyId + } if blockDeviceIsRoot(instanceBd, instance) { blockDevices["root"] = bd @@ -1335,9 +1362,6 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[st if instanceBd.DeviceName != nil { bd["device_name"] = *instanceBd.DeviceName } - if vol.Encrypted != nil { - bd["encrypted"] = *vol.Encrypted - } if vol.SnapshotId != nil { bd["snapshot_id"] = *vol.SnapshotId } @@ -1370,7 +1394,7 @@ func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { // For a bad image, we just return nil so we don't block a refresh if len(res.Images) == 0 { - return nil, nil + return nil, fmt.Errorf("No images found for AMI %s", ami) } image := res.Images[0] @@ -1378,7 +1402,7 @@ func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) { // Instance store backed AMIs do not provide a root device name. if *image.RootDeviceType == ec2.DeviceTypeInstanceStore { - return nil, nil + return nil, fmt.Errorf("Instance store backed AMIs do not provide a root device name - Use an EBS AMI") } // Some AMIs have a RootDeviceName like "/dev/sda1" that does not appear as a @@ -1498,6 +1522,10 @@ func readBlockDeviceMappingsFromConfig( ebs.Encrypted = aws.Bool(v) } + if v, ok := bd["kms_key_id"].(string); ok && v != "" { + ebs.KmsKeyId = aws.String(v) + } + if v, ok := bd["volume_size"].(int); ok && v != 0 { ebs.VolumeSize = aws.Int64(int64(v)) } @@ -1555,6 +1583,14 @@ func readBlockDeviceMappingsFromConfig( DeleteOnTermination: aws.Bool(bd["delete_on_termination"].(bool)), } + if v, ok := bd["encrypted"].(bool); ok && v { + ebs.Encrypted = aws.Bool(v) + } + + if v, ok := bd["kms_key_id"].(string); ok && v != "" { + ebs.KmsKeyId = aws.String(bd["kms_key_id"].(string)) + } + if v, ok := bd["volume_size"].(int); ok && v != 0 { ebs.VolumeSize = aws.Int64(int64(v)) } @@ -1575,20 +1611,15 @@ func readBlockDeviceMappingsFromConfig( log.Print("[WARN] IOPs is only valid for storate type io1 for EBS Volumes") } - if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil { - if dn == nil { - return nil, fmt.Errorf( - "Expected 1 AMI for ID: %s, got none", - d.Get("ami").(string)) - } - - blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ - DeviceName: dn, - Ebs: ebs, - }) - } else { - return nil, err + dn, err := fetchRootDeviceName(d.Get("ami").(string), conn) + if err != nil { + return nil, fmt.Errorf("Expected 1 AMI for ID: %s (%s)", d.Get("ami").(string), err) } + + blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{ + DeviceName: dn, + Ebs: ebs, + }) } } diff --git a/aws/resource_aws_instance_test.go b/aws/resource_aws_instance_test.go index 9b282141984..260d67be5b3 100644 --- a/aws/resource_aws_instance_test.go +++ b/aws/resource_aws_instance_test.go @@ -120,10 +120,7 @@ func TestFetchRootDevice(t *testing.T) { data := r.Data.(*ec2.DescribeImagesOutput) data.Images = tc.images }) - name, err := fetchRootDeviceName("ami-123", conn) - if err != nil { - t.Errorf("Error fetching device name: %s", err) - } + name, _ := fetchRootDeviceName("ami-123", conn) if tc.name != aws.StringValue(name) { t.Errorf("Expected name %s, got %s", tc.name, aws.StringValue(name)) } @@ -320,6 +317,52 @@ func TestAccAWSInstance_basic(t *testing.T) { }) } +func TestAccAWSInstance_EbsBlockDevice_KmsKeyArn(t *testing.T) { + var instance ec2.Instance + kmsKeyResourceName := "aws_kms_key.foo" + resourceName := "aws_instance.foo" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfigEbsBlockDeviceKmsKeyArn, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "ebs_block_device.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ebs_block_device.2634515331.encrypted", "true"), + resource.TestCheckResourceAttrPair(resourceName, "ebs_block_device.2634515331.kms_key_id", kmsKeyResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccAWSInstance_RootBlockDevice_KmsKeyArn(t *testing.T) { + var instance ec2.Instance + kmsKeyResourceName := "aws_kms_key.foo" + resourceName := "aws_instance.foo" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfigRootBlockDeviceKmsKeyArn, + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &instance), + resource.TestCheckResourceAttr(resourceName, "root_block_device.#", "1"), + resource.TestCheckResourceAttr(resourceName, "root_block_device.0.encrypted", "true"), + resource.TestCheckResourceAttrPair(resourceName, "root_block_device.0.kms_key_id", kmsKeyResourceName, "arn"), + ), + }, + }, + }) +} + func TestAccAWSInstance_userDataBase64(t *testing.T) { var v ec2.Instance @@ -482,7 +525,7 @@ func TestAccAWSInstance_blockDevices(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.2576023345.volume_size", "9"), resource.TestCheckResourceAttr( - "aws_instance.foo", "ebs_block_device.2576023345.volume_type", "standard"), + "aws_instance.foo", "ebs_block_device.2576023345.volume_type", "gp2"), resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.2554893574.device_name", "/dev/sdc"), resource.TestMatchResourceAttr( @@ -495,6 +538,8 @@ func TestAccAWSInstance_blockDevices(t *testing.T) { "aws_instance.foo", "ebs_block_device.2554893574.iops", "100"), resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.2634515331.device_name", "/dev/sdd"), + resource.TestMatchResourceAttr( + "aws_instance.foo", "ebs_block_device.2634515331.volume_id", regexp.MustCompile("vol-[a-z0-9]+")), resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.2634515331.encrypted", "true"), resource.TestCheckResourceAttr( @@ -2480,10 +2525,12 @@ resource "aws_instance" "foo" { volume_type = "gp2" volume_size = 11 } + ebs_block_device { device_name = "/dev/sdb" volume_size = 9 } + ebs_block_device { device_name = "/dev/sdc" volume_size = 10 @@ -2495,7 +2542,7 @@ resource "aws_instance" "foo" { ebs_block_device { device_name = "/dev/sdd" volume_size = 12 - encrypted = true + encrypted = true } ephemeral_block_device { @@ -2780,6 +2827,71 @@ resource "aws_instance" "foo" { } ` +const testAccInstanceConfigEbsBlockDeviceKmsKeyArn = ` +resource "aws_kms_key" "foo" { + deletion_window_in_days = 7 +} + +resource "aws_instance" "foo" { + # us-west-2 + ami = "ami-55a7ea65" + + # In order to attach an encrypted volume to an instance you need to have an + # m3.medium or larger. See "Supported Instance Types" in: + # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html + instance_type = "m3.medium" + + root_block_device { + volume_type = "gp2" + volume_size = 11 + } + + # Encrypted ebs block device + ebs_block_device { + device_name = "/dev/sdd" + encrypted = true + kms_key_id = "${aws_kms_key.foo.arn}" + volume_size = 12 + } +} +` + +const testAccInstanceConfigRootBlockDeviceKmsKeyArn = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + + tags = { + Name = "terraform-testacc-instance-source-dest-enable" + } +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + + tags = { + Name = "tf-acc-instance-source-dest-enable" + } +} + +resource "aws_kms_key" "foo" { + deletion_window_in_days = 7 +} + +resource "aws_instance" "foo" { + ami = "ami-08692d171e3cf02d6" + instance_type = "t3.nano" + subnet_id = "${aws_subnet.foo.id}" + + root_block_device { + delete_on_termination = true + encrypted = true + kms_key_id = "${aws_kms_key.foo.arn}" + } +} +` + const testAccCheckInstanceConfigWithAttachedVolume = ` data "aws_ami" "debian_jessie_latest" { most_recent = true diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index 61af019ccb8..9671cbc6e68 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -67,6 +67,7 @@ interpolation. * `device_name` - The physical name of the device. * `encrypted` - If the EBS volume is encrypted. * `iops` - `0` If the EBS volume is not a provisioned IOPS image, otherwise the supported IOPS count. + * `kms_key_arn` - Amazon Resource Name (ARN) of KMS Key, if EBS volume is encrypted. * `snapshot_id` - The ID of the snapshot. * `volume_size` - The size of the volume, in GiB. * `volume_type` - The volume type. @@ -96,7 +97,9 @@ interpolation. * `public_ip` - The public IP address assigned to the Instance, if applicable. **NOTE**: If you are using an [`aws_eip`](/docs/providers/aws/r/eip.html) with your instance, you should refer to the EIP's address directly and not use `public_ip`, as this field will change after the EIP is attached. * `root_block_device` - The root block device mappings of the Instance * `delete_on_termination` - If the root block device will be deleted on termination. + * `encrypted` - If the EBS volume is encrypted. * `iops` - `0` If the volume is not a provisioned IOPS image, otherwise the supported IOPS count. + * `kms_key_arn` - Amazon Resource Name (ARN) of KMS Key, if EBS volume is encrypted. * `volume_size` - The size of the volume, in GiB. * `volume_type` - The type of the volume. * `security_groups` - The associated security groups. diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index d6ef4489546..7cb56a7905e 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -131,6 +131,8 @@ The `root_block_device` mapping supports the following: using that type * `delete_on_termination` - (Optional) Whether the volume should be destroyed on instance termination (Default: `true`). +* `encrypted` - (Optional) Enable volume encryption. (Default: `false`). Must be configured to perform drift detection. +* `kms_key_id` - (Optional) Amazon Resource Name (ARN) of the KMS Key to use when encrypting the volume. Must be configured to perform drift detection. Modifying any of the `root_block_device` settings requires resource replacement. @@ -149,7 +151,8 @@ Each `ebs_block_device` supports the following: on instance termination (Default: `true`). * `encrypted` - (Optional) Enables [EBS encryption](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html) - on the volume (Default: `false`). Cannot be used with `snapshot_id`. + on the volume (Default: `false`). Cannot be used with `snapshot_id`. Must be configured to perform drift detection. +* `kms_key_id` - (Optional) Amazon Resource Name (ARN) of the KMS Key to use when encrypting the volume. Must be configured to perform drift detection. ~> **NOTE:** Currently, changes to the `ebs_block_device` configuration of _existing_ resources cannot be automatically detected by Terraform. To manage changes and attachments of an EBS block to an instance, use the `aws_ebs_volume` and `aws_volume_attachment` resources instead. If you use `ebs_block_device` on an `aws_instance`, Terraform will assume management over the full set of non-root EBS block devices for the instance, treating additional block devices as drift. For this reason, `ebs_block_device` cannot be mixed with external `aws_ebs_volume` and `aws_volume_attachment` resources for a given instance.