From 449a98a39865d91620eff4aebb664265ca408e13 Mon Sep 17 00:00:00 2001 From: Dana Hoffman Date: Wed, 18 Jan 2017 05:49:48 -0800 Subject: [PATCH] providers/google: Add support for encrypting a disk (#11167) * providers/google: add support for encrypting a disk * providers/google: Add docs for encrypting disks * providers/google: CSEK small fixes: sensitive params and mismatched state files --- .../providers/google/resource_compute_disk.go | 20 +++++ .../google/resource_compute_disk_test.go | 54 +++++++++++++ .../google/resource_compute_instance.go | 35 +++++++++ .../google/resource_compute_instance_test.go | 78 +++++++++++++++++++ .../google/r/compute_disk.html.markdown | 10 +++ .../google/r/compute_instance.html.markdown | 9 +++ 6 files changed, 206 insertions(+) diff --git a/builtin/providers/google/resource_compute_disk.go b/builtin/providers/google/resource_compute_disk.go index 5984383f7..c8ef8007a 100644 --- a/builtin/providers/google/resource_compute_disk.go +++ b/builtin/providers/google/resource_compute_disk.go @@ -28,6 +28,18 @@ func resourceComputeDisk() *schema.Resource { ForceNew: true, }, + "disk_encryption_key_raw": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + + "disk_encryption_key_sha256": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "image": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -129,6 +141,11 @@ func resourceComputeDiskCreate(d *schema.ResourceData, meta interface{}) error { disk.SourceSnapshot = snapshotData.SelfLink } + if v, ok := d.GetOk("disk_encryption_key_raw"); ok { + disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{} + disk.DiskEncryptionKey.RawKey = v.(string) + } + op, err := config.clientCompute.Disks.Insert( project, d.Get("zone").(string), disk).Do() if err != nil { @@ -168,6 +185,9 @@ func resourceComputeDiskRead(d *schema.ResourceData, meta interface{}) error { } d.Set("self_link", disk.SelfLink) + if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" { + d.Set("disk_encryption_key_sha256", disk.DiskEncryptionKey.Sha256) + } return nil } diff --git a/builtin/providers/google/resource_compute_disk_test.go b/builtin/providers/google/resource_compute_disk_test.go index e18cb9945..478144e7e 100644 --- a/builtin/providers/google/resource_compute_disk_test.go +++ b/builtin/providers/google/resource_compute_disk_test.go @@ -30,6 +30,28 @@ func TestAccComputeDisk_basic(t *testing.T) { }) } +func TestAccComputeDisk_encryption(t *testing.T) { + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_encryption(diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", &disk), + testAccCheckEncryptionKey( + "google_compute_disk.foobar", &disk), + ), + }, + }, + }) +} + func testAccCheckComputeDiskDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -77,6 +99,26 @@ func testAccCheckComputeDiskExists(n string, disk *compute.Disk) resource.TestCh } } +func testAccCheckEncryptionKey(n string, disk *compute.Disk) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + attr := rs.Primary.Attributes["disk_encryption_key_sha256"] + if disk.DiskEncryptionKey == nil && attr != "" { + return fmt.Errorf("Disk %s has mismatched encryption key.\nTF State: %+v\nGCP State: ", n, attr) + } + + if attr != disk.DiskEncryptionKey.Sha256 { + return fmt.Errorf("Disk %s has mismatched encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, disk.DiskEncryptionKey.Sha256) + } + return nil + } +} + func testAccComputeDisk_basic(diskName string) string { return fmt.Sprintf(` resource "google_compute_disk" "foobar" { @@ -87,3 +129,15 @@ resource "google_compute_disk" "foobar" { zone = "us-central1-a" }`, diskName) } + +func testAccComputeDisk_encryption(diskName string) string { + return fmt.Sprintf(` +resource "google_compute_disk" "foobar" { + name = "%s" + image = "debian-8-jessie-v20160803" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" + disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" +}`, diskName) +} diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 40970cfc5..c25cd87c6 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -75,6 +75,18 @@ func resourceComputeInstance() *schema.Resource { Type: schema.TypeString, Optional: true, }, + + "disk_encryption_key_raw": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + }, + + "disk_encryption_key_sha256": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, }, }, }, @@ -437,6 +449,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err disk.DeviceName = v.(string) } + if v, ok := d.GetOk(prefix + ".disk_encryption_key_raw"); ok { + disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{} + disk.DiskEncryptionKey.RawKey = v.(string) + } + disks = append(disks, &disk) } @@ -770,6 +787,24 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("tags_fingerprint", instance.Tags.Fingerprint) } + disks := make([]map[string]interface{}, 0, 1) + for i, disk := range instance.Disks { + di := map[string]interface{}{ + "disk": d.Get(fmt.Sprintf("disk.%d.disk", i)), + "image": d.Get(fmt.Sprintf("disk.%d.image", i)), + "type": d.Get(fmt.Sprintf("disk.%d.type", i)), + "scratch": d.Get(fmt.Sprintf("disk.%d.scratch", i)), + "auto_delete": d.Get(fmt.Sprintf("disk.%d.auto_delete", i)), + "size": d.Get(fmt.Sprintf("disk.%d.size", i)), + "device_name": d.Get(fmt.Sprintf("disk.%d.device_name", i)), + } + if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" { + di["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256 + } + disks = append(disks, di) + } + d.Set("disk", disks) + d.Set("self_link", instance.SelfLink) d.SetId(instance.Name) diff --git a/builtin/providers/google/resource_compute_instance_test.go b/builtin/providers/google/resource_compute_instance_test.go index 2a254b911..382e5c710 100644 --- a/builtin/providers/google/resource_compute_instance_test.go +++ b/builtin/providers/google/resource_compute_instance_test.go @@ -220,6 +220,30 @@ func TestAccComputeInstance_disksWithAutodelete(t *testing.T) { }) } +func TestAccComputeInstance_diskEncryption(t *testing.T) { + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_disks_encryption(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + testAccCheckComputeInstanceDisk(&instance, diskName, true, false), + testAccCheckComputeInstanceDiskEncryptionKey("google_compute_instance.foobar", &instance), + ), + }, + }, + }) +} + func TestAccComputeInstance_local_ssd(t *testing.T) { var instance compute.Instance var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) @@ -636,6 +660,27 @@ func testAccCheckComputeInstanceDisk(instance *compute.Instance, source string, } } +func testAccCheckComputeInstanceDiskEncryptionKey(n string, instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + for i, disk := range instance.Disks { + attr := rs.Primary.Attributes[fmt.Sprintf("disk.%d.disk_encryption_key_sha256", i)] + if disk.DiskEncryptionKey == nil && attr != "" { + return fmt.Errorf("Disk %d has mismatched encryption key.\nTF State: %+v\nGCP State: ", i, attr) + } + if disk.DiskEncryptionKey != nil && attr != disk.DiskEncryptionKey.Sha256 { + return fmt.Errorf("Disk %d has mismatched encryption key.\nTF State: %+v\nGCP State: %+v", + i, attr, disk.DiskEncryptionKey.Sha256) + } + } + return nil + } +} + func testAccCheckComputeInstanceTag(instance *compute.Instance, n string) resource.TestCheckFunc { return func(s *terraform.State) error { if instance.Tags == nil { @@ -983,6 +1028,39 @@ func testAccComputeInstance_disks(disk, instance string, autodelete bool) string }`, disk, instance, autodelete) } +func testAccComputeInstance_disks_encryption(disk, instance string) string { + return fmt.Sprintf(` + resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + } + + resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + disk { + image = "debian-8-jessie-v20160803" + disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + } + + disk { + disk = "${google_compute_disk.foobar.name}" + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + }`, disk, instance) +} + func testAccComputeInstance_local_ssd(instance string) string { return fmt.Sprintf(` resource "google_compute_instance" "local-ssd" { diff --git a/website/source/docs/providers/google/r/compute_disk.html.markdown b/website/source/docs/providers/google/r/compute_disk.html.markdown index 3d61dd6b8..faeb20cc7 100644 --- a/website/source/docs/providers/google/r/compute_disk.html.markdown +++ b/website/source/docs/providers/google/r/compute_disk.html.markdown @@ -32,6 +32,11 @@ The following arguments are supported: - - - +* `disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to encrypt this disk. + * `image` - (Optional) The image from which to initialize this disk. Either the full URL, a contraction of the form "project/name", or just a name (in which case the current project is used). @@ -51,4 +56,9 @@ The following arguments are supported: In addition to the arguments listed above, the following computed attributes are exported: +* `disk_encryption_key_sha256` - The [RFC 4648 base64] + (https://tools.ietf.org/html/rfc4648#section-4) encoded SHA-256 hash of the + [customer-supplied encryption key](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) + that protects this resource. + * `self_link` - The URI of the created resource. diff --git a/website/source/docs/providers/google/r/compute_instance.html.markdown b/website/source/docs/providers/google/r/compute_instance.html.markdown index 7b7031817..b015f1510 100644 --- a/website/source/docs/providers/google/r/compute_instance.html.markdown +++ b/website/source/docs/providers/google/r/compute_instance.html.markdown @@ -136,6 +136,11 @@ the type is "local-ssd", in which case scratch must be true). * `device_name` - (Optional) Name with which attached disk will be accessible under `/dev/disk/by-id/` +* `disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to encrypt this disk. + The `network_interface` block supports: * `network` - (Optional) The name or self_link of the network to attach this interface to. @@ -204,3 +209,7 @@ exported: * `network_interface.0.address` - The internal ip address of the instance, either manually or dynamically assigned. * `network_interface.0.access_config.0.assigned_nat_ip` - If the instance has an access config, either the given external ip (in the `nat_ip` field) or the ephemeral (generated) ip (if you didn't provide one). + +* `disk.0.disk_encryption_key_sha256` - The [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + encoded SHA-256 hash of the [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) that protects this resource. \ No newline at end of file