diff --git a/build/terraform b/build/terraform index 3ce24ce089e7..afcaa8cb3a8d 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 3ce24ce089e7c07254c2f56026a0e406b083afbe +Subproject commit afcaa8cb3a8da4191b0aa5fddcfb79150743ce6e diff --git a/build/terraform-beta b/build/terraform-beta index 7fea703fff88..9b068f3c0826 160000 --- a/build/terraform-beta +++ b/build/terraform-beta @@ -1 +1 @@ -Subproject commit 7fea703fff88257cfb144a0a8e97b2fba2bb5828 +Subproject commit 9b068f3c08261c4aec5d3d4d39023faf18963b7a diff --git a/products/compute/api.yaml b/products/compute/api.yaml index 337381d54768..d4da1118d3f8 100644 --- a/products/compute/api.yaml +++ b/products/compute/api.yaml @@ -627,6 +627,12 @@ objects: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. input: true - !ruby/object:Api::Type::String name: 'sourceImageId' @@ -1702,6 +1708,12 @@ objects: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. # TODO(alexstephen): Change to ResourceRef with array support - !ruby/object:Api::Type::Array name: 'licenses' @@ -1768,6 +1780,12 @@ objects: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. - !ruby/object:Api::Type::String name: 'sourceDiskId' description: | @@ -2995,6 +3013,12 @@ objects: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. input: true - !ruby/object:Api::Type::NestedObject name: 'sourceDiskEncryptionKey' @@ -3014,6 +3038,12 @@ objects: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. input: true properties: - !ruby/object:Api::Type::Time diff --git a/products/compute/disk_parameters.yaml b/products/compute/disk_parameters.yaml index 7e7bdc39a31b..3b3fe3190b90 100644 --- a/products/compute/disk_parameters.yaml +++ b/products/compute/disk_parameters.yaml @@ -37,6 +37,12 @@ The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. output: true + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. input: true - !ruby/object:Api::Type::ResourceRef name: 'sourceSnapshot' @@ -62,6 +68,12 @@ description: | Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + # TODO(chrisst) Change to ResourceRef once KMS is in Magic Modules + name: 'kmsKeyName' + min_version: beta + description: | + The name of the encryption key that is stored in Google Cloud KMS. - !ruby/object:Api::Type::String name: 'sha256' description: | diff --git a/products/compute/terraform.yaml b/products/compute/terraform.yaml index 84c7aeef972b..f92a9957394c 100644 --- a/products/compute/terraform.yaml +++ b/products/compute/terraform.yaml @@ -114,6 +114,30 @@ overrides: !ruby/object:Provider::ResourceOverrides override_order: -1 name: image diff_suppress_func: 'diskImageDiffSuppress' + diskEncryptionKey.kmsKeyName: !ruby/object:Provider::Terraform::PropertyOverride + diff_suppress_func: 'compareSelfLinkRelativePaths' + name: "kmsKeySelfLink" + description: | + The self link of the encryption key used to encrypt the disk. Also called KmsKeyName + in the cloud console. In order to use this additional + IAM permissions need to be set on the Compute Engine Service Agent. See + https://cloud.google.com/compute/docs/disks/customer-managed-encryption#encrypt_a_new_persistent_disk_with_your_own_keys + sourceSnapshotEncryptionKey.kmsKeyName: !ruby/object:Provider::Terraform::PropertyOverride + diff_suppress_func: 'compareSelfLinkRelativePaths' + name: "kmsKeySelfLink" + description: | + The self link of the encryption key used to encrypt the disk. Also called KmsKeyName + in the cloud console. In order to use this additional + IAM permissions need to be set on the Compute Engine Service Agent. See + https://cloud.google.com/compute/docs/disks/customer-managed-encryption#encrypt_a_new_persistent_disk_with_your_own_keys + sourceImageEncryptionKey.kmsKeyName: !ruby/object:Provider::Terraform::PropertyOverride + diff_suppress_func: 'compareSelfLinkRelativePaths' + name: "kmsKeySelfLink" + description: | + The self link of the encryption key used to encrypt the disk. Also called KmsKeyName + in the cloud console. In order to use this additional + IAM permissions need to be set on the Compute Engine Service Agent. See + https://cloud.google.com/compute/docs/disks/customer-managed-encryption#encrypt_a_new_persistent_disk_with_your_own_keys image: !ruby/object:Provider::Terraform::PropertyOverride override_order: 5 description: | diff --git a/provider/terraform/tests/resource_compute_disk_test.go.erb b/provider/terraform/tests/resource_compute_disk_test.go.erb index d315646e91cc..d03cfc85423a 100644 --- a/provider/terraform/tests/resource_compute_disk_test.go.erb +++ b/provider/terraform/tests/resource_compute_disk_test.go.erb @@ -221,7 +221,7 @@ func TestAccComputeDisk_basic(t *testing.T) { Config: testAccComputeDisk_basic(diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foobar", &disk), + "google_compute_disk.foobar", getTestProjectFromEnv(), &disk), testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-label-value"), testAccCheckComputeDiskHasLabelFingerprint(&disk, "google_compute_disk.foobar"), ), @@ -265,7 +265,7 @@ func TestAccComputeDisk_update(t *testing.T) { Config: testAccComputeDisk_basic(diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foobar", &disk), + "google_compute_disk.foobar", getTestProjectFromEnv(), &disk), resource.TestCheckResourceAttr("google_compute_disk.foobar", "size", "50"), testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-label-value"), testAccCheckComputeDiskHasLabelFingerprint(&disk, "google_compute_disk.foobar"), @@ -275,7 +275,7 @@ func TestAccComputeDisk_update(t *testing.T) { Config: testAccComputeDisk_updated(diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foobar", &disk), + "google_compute_disk.foobar", getTestProjectFromEnv(), &disk), resource.TestCheckResourceAttr("google_compute_disk.foobar", "size", "100"), testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-updated-label-value"), testAccCheckComputeDiskHasLabel(&disk, "a-new-label", "a-new-label-value"), @@ -305,14 +305,14 @@ func TestAccComputeDisk_fromSnapshot(t *testing.T) { Config: testAccComputeDisk_fromSnapshot(projectName, firstDiskName, snapshotName, diskName, "self_link"), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.seconddisk", &disk), + "google_compute_disk.seconddisk", getTestProjectFromEnv(), &disk), ), }, resource.TestStep{ Config: testAccComputeDisk_fromSnapshot(projectName, firstDiskName, snapshotName, diskName, "name"), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.seconddisk", &disk), + "google_compute_disk.seconddisk", getTestProjectFromEnv(), &disk), ), }, }, @@ -334,14 +334,52 @@ func TestAccComputeDisk_encryption(t *testing.T) { Config: testAccComputeDisk_encryption(diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( + "google_compute_disk.foobar", getTestProjectFromEnv(), &disk), + testAccCheckEncryptionKey( "google_compute_disk.foobar", &disk), + ), + }, + }, + }) +} + +<% unless version.nil? || version == 'beta' -%> +func TestAccComputeDisk_encryptionKMS(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "tf-test-" + acctest.RandString(10) + billingAccount := getTestBillingAccountFromEnv(t) + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + keyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + importID := fmt.Sprintf("%s/%s/%s", pid, "us-central1-a", diskName) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_encryptionKMS(pid, pname, org, billingAccount, diskName, keyRingName, keyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", pid, &disk), testAccCheckEncryptionKey( "google_compute_disk.foobar", &disk), ), }, + resource.TestStep{ + ResourceName: "google_compute_disk.foobar", + ImportStateId: importID, + ImportState: true, + ImportStateVerify: true, + }, }, }) } +<% end -%> func TestAccComputeDisk_deleteDetach(t *testing.T) { t.Parallel() @@ -359,7 +397,7 @@ func TestAccComputeDisk_deleteDetach(t *testing.T) { Config: testAccComputeDisk_deleteDetach(instanceName, diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), ), }, // this needs to be a second step so we refresh and see the instance @@ -370,7 +408,7 @@ func TestAccComputeDisk_deleteDetach(t *testing.T) { Config: testAccComputeDisk_deleteDetach(instanceName, diskName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), testAccCheckComputeDiskInstances( "google_compute_disk.foo", &disk), ), @@ -396,7 +434,7 @@ func TestAccComputeDisk_deleteDetachIGM(t *testing.T) { Config: testAccComputeDisk_deleteDetachIGM(diskName, mgrName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), ), }, // this needs to be a second step so we refresh and see the instance @@ -407,7 +445,7 @@ func TestAccComputeDisk_deleteDetachIGM(t *testing.T) { Config: testAccComputeDisk_deleteDetachIGM(diskName, mgrName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), testAccCheckComputeDiskInstances( "google_compute_disk.foo", &disk), ), @@ -417,7 +455,7 @@ func TestAccComputeDisk_deleteDetachIGM(t *testing.T) { Config: testAccComputeDisk_deleteDetachIGM(diskName2, mgrName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), ), }, // Add the extra step like before @@ -425,7 +463,7 @@ func TestAccComputeDisk_deleteDetachIGM(t *testing.T) { Config: testAccComputeDisk_deleteDetachIGM(diskName2, mgrName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeDiskExists( - "google_compute_disk.foo", &disk), + "google_compute_disk.foo", getTestProjectFromEnv(), &disk), testAccCheckComputeDiskInstances( "google_compute_disk.foo", &disk), ), @@ -484,9 +522,8 @@ func testAccCheckComputeDiskDestroy(s *terraform.State) error { return nil } -func testAccCheckComputeDiskExists(n string, disk *compute.Disk) resource.TestCheckFunc { +func testAccCheckComputeDiskExists(n, p string, disk *compute.Disk) resource.TestCheckFunc { return func(s *terraform.State) error { - p := getTestProjectFromEnv() rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) @@ -694,6 +731,83 @@ resource "google_compute_disk" "foobar" { }`, diskName) } +func testAccComputeDisk_encryptionKMS(pid, pname, org, billing, diskName, keyRingName, keyName string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_project_services" "apis" { + project = "${google_project.project.project_id}" + + services = [ + "oslogin.googleapis.com", + "compute.googleapis.com", + "cloudkms.googleapis.com", + "appengine.googleapis.com", + ] +} + +resource "google_project_iam_member" "kms-project-binding" { + project = "${google_project.project.project_id}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${google_project.project.number}@compute-system.iam.gserviceaccount.com" + + depends_on = ["google_project_services.apis"] +} + +resource "google_kms_crypto_key_iam_binding" "kms-key-binding" { + crypto_key_id = "${google_kms_crypto_key.my_crypto_key.self_link}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + + members = [ + "serviceAccount:service-${google_project.project.number}@compute-system.iam.gserviceaccount.com", + ] + + depends_on = ["google_project_services.apis"] +} + +resource "google_kms_key_ring" "my_key_ring" { + name = "%s" + project = "${google_project.project.project_id}" + location = "us-central1" + + depends_on = ["google_project_services.apis"] +} + +resource "google_kms_crypto_key" "my_crypto_key" { + name = "%s" + key_ring = "${google_kms_key_ring.my_key_ring.self_link}" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + project = "${google_project.project.project_id}" + + disk_encryption_key { + kms_key_self_link = "${google_kms_crypto_key.my_crypto_key.self_link}" + } + + depends_on = [ + "google_kms_crypto_key_iam_binding.kms-key-binding", + "google_project_iam_member.kms-project-binding", + ] +} +`, pid, pname, org, billing, keyRingName, keyName, diskName) +} + func testAccComputeDisk_deleteDetach(instanceName, diskName string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { diff --git a/templates/terraform/decoders/disk.erb b/templates/terraform/decoders/disk.erb index eee89387bc96..8bc1209f49d3 100644 --- a/templates/terraform/decoders/disk.erb +++ b/templates/terraform/decoders/disk.erb @@ -4,6 +4,11 @@ if v, ok := res["diskEncryptionKey"]; ok { // The raw key won't be returned, so we need to use the original. transformed["rawKey"] = d.Get("disk_encryption_key.0.raw_key") transformed["sha256"] = original["sha256"] + <% unless version.nil? || version == 'ga' %> + // The response for crypto keys often includes the version of the key which needs to be removed + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + transformed["kmsKeyName"] = strings.Split(original["kmsKeyName"].(string), "/cryptoKeyVersions")[0] + <% end %> res["diskEncryptionKey"] = transformed } @@ -13,6 +18,11 @@ if v, ok := res["sourceImageEncryptionKey"]; ok { // The raw key won't be returned, so we need to use the original. transformed["rawKey"] = d.Get("source_image_encryption_key.0.raw_key") transformed["sha256"] = original["sha256"] + <% unless version.nil? || version == 'ga' %> + // The response for crypto keys often includes the version of the key which needs to be removed + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + transformed["kmsKeyName"] = strings.Split(original["kmsKeyName"].(string), "/cryptoKeyVersions")[0] + <% end %> res["sourceImageEncryptionKey"] = transformed } @@ -22,6 +32,11 @@ if v, ok := res["sourceSnapshotEncryptionKey"]; ok { // The raw key won't be returned, so we need to use the original. transformed["rawKey"] = d.Get("source_snapshot_encryption_key.0.raw_key") transformed["sha256"] = original["sha256"] + <% unless version.nil? || version == 'ga' %> + // The response for crypto keys often includes the version of the key which needs to be removed + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + transformed["kmsKeyName"] = strings.Split(original["kmsKeyName"].(string), "/cryptoKeyVersions")[0] + <% end %> res["sourceSnapshotEncryptionKey"] = transformed }