From ddb698562ebd983dc2fa5b05124aeeced5b3d5c5 Mon Sep 17 00:00:00 2001 From: Arthur de Lapertosa Lisboa Date: Mon, 9 Sep 2024 12:43:51 -0300 Subject: [PATCH] feat: add confidential computing example (#421) --- examples/confidential_computing/README.md | 30 ++++++ examples/confidential_computing/main.tf | 98 +++++++++++++++++++ examples/confidential_computing/outputs.tf | 36 +++++++ examples/confidential_computing/variables.tf | 59 +++++++++++ metadata.yaml | 2 + modules/compute_disk_snapshot/README.md | 4 +- modules/compute_disk_snapshot/metadata.yaml | 6 +- modules/compute_disk_snapshot/outputs.tf | 4 +- modules/compute_instance/metadata.yaml | 2 + modules/instance_template/metadata.yaml | 2 + modules/mig/metadata.yaml | 2 + modules/mig_with_percent/metadata.yaml | 2 + .../metadata.yaml | 2 + modules/umig/metadata.yaml | 2 + .../confidential_compute_instance/main.tf | 25 +++++ .../confidential_compute_instance/network.tf | 1 + .../confidential_compute_instance/outputs.tf | 40 ++++++++ .../variables.tf | 20 ++++ .../confidential_compute_instance/versions.tf | 19 ++++ .../confidential_compute_instance_test.go | 54 ++++++++++ 20 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 examples/confidential_computing/README.md create mode 100644 examples/confidential_computing/main.tf create mode 100644 examples/confidential_computing/outputs.tf create mode 100644 examples/confidential_computing/variables.tf create mode 100644 test/fixtures/confidential_compute_instance/main.tf create mode 120000 test/fixtures/confidential_compute_instance/network.tf create mode 100644 test/fixtures/confidential_compute_instance/outputs.tf create mode 100644 test/fixtures/confidential_compute_instance/variables.tf create mode 100644 test/fixtures/confidential_compute_instance/versions.tf create mode 100644 test/integration/confidential_compute_instance/confidential_compute_instance_test.go diff --git a/examples/confidential_computing/README.md b/examples/confidential_computing/README.md new file mode 100644 index 00000000..ca5b2a77 --- /dev/null +++ b/examples/confidential_computing/README.md @@ -0,0 +1,30 @@ +# confidential computing vm + +This is an example of a vm creation with confidential computing, +encrypted disk using a multiregion (US by default) Cloud HSM key +and a custom service account with cloud-platform scope. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| key | Key name. | `string` | n/a | yes | +| keyring | Keyring name. | `string` | n/a | yes | +| location | Location for the resources (keyring, key, network, etc.). | `string` | `"us"` | no | +| project\_id | The Google Cloud project ID. | `string` | n/a | yes | +| region | The GCP region to create and test resources in. | `string` | `"us-central1"` | no | +| service\_account\_roles | Predefined roles for the Service account that will be created for the VM. Remember to follow principles of least privileges with Cloud IAM. | `list(string)` | `[]` | no | +| subnetwork | The subnetwork selflink to host the compute instances in. | `string` | n/a | yes | +| suffix | A suffix to be used as an identifier for resources. (e.g., suffix for KMS Key, Keyring). | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| instance\_self\_link | Self-link for compute instance. | +| name | Name of the instance templates. | +| self\_link | Self-link to the instance template. | +| suffix | Suffix used as an identifier for resources. | + + diff --git a/examples/confidential_computing/main.tf b/examples/confidential_computing/main.tf new file mode 100644 index 00000000..e1309736 --- /dev/null +++ b/examples/confidential_computing/main.tf @@ -0,0 +1,98 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + default_suffix = var.suffix == "" ? random_string.suffix.result : "${random_string.suffix.result}-${var.suffix}" + key_name = "${var.key}-${local.default_suffix}" +} + +resource "random_string" "suffix" { + length = 4 + special = false + upper = false +} + +module "kms" { + source = "terraform-google-modules/kms/google" + version = "2.3.0" + + keyring = "${var.keyring}-${local.default_suffix}" + location = var.location + project_id = var.project_id + keys = [local.key_name] + purpose = "ENCRYPT_DECRYPT" + key_protection_level = "HSM" + prevent_destroy = false +} + +resource "google_service_account" "default" { + project = var.project_id + account_id = "confidential-compute-sa" + display_name = "Custom SA for confidential VM Instance" +} + +resource "google_project_iam_member" "service_account_roles" { + for_each = toset(var.service_account_roles) + + project = var.project_id + role = each.key + member = "serviceAccount:${google_service_account.default.email}" +} + +data "google_project" "project" { + project_id = var.project_id +} + +resource "google_kms_crypto_key_iam_binding" "crypto_key" { + crypto_key_id = module.kms.keys[local.key_name] + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + members = [ + "serviceAccount:service-${data.google_project.project.number}@compute-system.iam.gserviceaccount.com", + ] +} + +module "instance_template" { + source = "../../modules/instance_template" + + region = var.region + project_id = var.project_id + subnetwork = var.subnetwork + + name_prefix = "confidential-encrypted-template" + source_image_project = "ubuntu-os-cloud" + source_image = "ubuntu-2004-lts" + machine_type = "n2d-standard-2" + min_cpu_platform = "AMD Milan" + enable_confidential_vm = true + confidential_instance_type = "SEV" + + service_account = { + email = google_service_account.default.email + scopes = ["cloud-platform"] + } + disk_encryption_key = module.kms.keys[local.key_name] +} + +module "compute_instance" { + source = "terraform-google-modules/vm/google//modules/compute_instance" + version = "~> 11.0" + + region = var.region + subnetwork = var.subnetwork + hostname = "confidential-encrypted-instance" + instance_template = module.instance_template.self_link + deletion_protection = false +} diff --git a/examples/confidential_computing/outputs.tf b/examples/confidential_computing/outputs.tf new file mode 100644 index 00000000..6bcf2e82 --- /dev/null +++ b/examples/confidential_computing/outputs.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +output "self_link" { + description = "Self-link to the instance template." + value = module.instance_template.self_link +} + +output "name" { + description = "Name of the instance templates." + value = module.instance_template.name +} + +output "instance_self_link" { + description = "Self-link for compute instance." + value = module.compute_instance.instances_self_links[0] +} + +output "suffix" { + description = "Suffix used as an identifier for resources." + value = local.default_suffix +} diff --git a/examples/confidential_computing/variables.tf b/examples/confidential_computing/variables.tf new file mode 100644 index 00000000..6fe70f28 --- /dev/null +++ b/examples/confidential_computing/variables.tf @@ -0,0 +1,59 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The Google Cloud project ID." + type = string +} + +variable "region" { + description = "The GCP region to create and test resources in." + type = string + default = "us-central1" +} + +variable "subnetwork" { + description = "The subnetwork selflink to host the compute instances in." + type = string +} + +variable "location" { + description = "Location for the resources (keyring, key, network, etc.)." + type = string + default = "us" +} + +variable "suffix" { + description = "A suffix to be used as an identifier for resources. (e.g., suffix for KMS Key, Keyring)." + type = string + default = "" +} + +variable "keyring" { + description = "Keyring name." + type = string +} + +variable "key" { + description = "Key name." + type = string +} + +variable "service_account_roles" { + description = "Predefined roles for the Service account that will be created for the VM. Remember to follow principles of least privileges with Cloud IAM." + type = list(string) + default = [] +} diff --git a/metadata.yaml b/metadata.yaml index 23332753..b7ccf310 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/compute_disk_snapshot/README.md b/modules/compute_disk_snapshot/README.md index 07658c0a..4b9fcda5 100644 --- a/modules/compute_disk_snapshot/README.md +++ b/modules/compute_disk_snapshot/README.md @@ -26,7 +26,7 @@ See the [disk snapshot](https://github.com/terraform-google-modules/terraform-go | Name | Description | |------|-------------| -| attachments | Disk attachments to the resource policy | -| policy | Resource snapshot policy details | +| attachments | Disk attachments to the resource policy. | +| policy | Resource snapshot policy details. | diff --git a/modules/compute_disk_snapshot/metadata.yaml b/modules/compute_disk_snapshot/metadata.yaml index b536f0eb..1d796b72 100644 --- a/modules/compute_disk_snapshot/metadata.yaml +++ b/modules/compute_disk_snapshot/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot @@ -148,9 +150,9 @@ spec: required: true outputs: - name: attachments - description: Disk attachments to the resource policy + description: Disk attachments to the resource policy. - name: policy - description: Resource snapshot policy details + description: Resource snapshot policy details. requirements: roles: - level: Project diff --git a/modules/compute_disk_snapshot/outputs.tf b/modules/compute_disk_snapshot/outputs.tf index b40e408c..c1489441 100644 --- a/modules/compute_disk_snapshot/outputs.tf +++ b/modules/compute_disk_snapshot/outputs.tf @@ -15,11 +15,11 @@ */ output "policy" { - description = "Resource snapshot policy details" + description = "Resource snapshot policy details." value = google_compute_resource_policy.policy } output "attachments" { - description = "Disk attachments to the resource policy" + description = "Disk attachments to the resource policy." value = google_compute_disk_resource_policy_attachment.attachment[*] } diff --git a/modules/compute_instance/metadata.yaml b/modules/compute_instance/metadata.yaml index f8002ab7..3b13695c 100644 --- a/modules/compute_instance/metadata.yaml +++ b/modules/compute_instance/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/instance_template/metadata.yaml b/modules/instance_template/metadata.yaml index 4dd52c84..75630159 100644 --- a/modules/instance_template/metadata.yaml +++ b/modules/instance_template/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/mig/metadata.yaml b/modules/mig/metadata.yaml index fbecdc82..78173971 100644 --- a/modules/mig/metadata.yaml +++ b/modules/mig/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/mig_with_percent/metadata.yaml b/modules/mig_with_percent/metadata.yaml index 9baa487d..90c0102f 100644 --- a/modules/mig_with_percent/metadata.yaml +++ b/modules/mig_with_percent/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/preemptible_and_regular_instance_templates/metadata.yaml b/modules/preemptible_and_regular_instance_templates/metadata.yaml index ede089cd..d334879c 100644 --- a/modules/preemptible_and_regular_instance_templates/metadata.yaml +++ b/modules/preemptible_and_regular_instance_templates/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/modules/umig/metadata.yaml b/modules/umig/metadata.yaml index 588ac192..ced71e18 100644 --- a/modules/umig/metadata.yaml +++ b/modules/umig/metadata.yaml @@ -38,6 +38,8 @@ spec: location: examples/instance_template/alias_ip_range - name: autoscaler location: examples/mig/autoscaler + - name: confidential_computing + location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing - name: disk_snapshot diff --git a/test/fixtures/confidential_compute_instance/main.tf b/test/fixtures/confidential_compute_instance/main.tf new file mode 100644 index 00000000..d42751bd --- /dev/null +++ b/test/fixtures/confidential_compute_instance/main.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module "confidential_computing" { + source = "../../../examples/confidential_computing" + project_id = var.project_id + region = "us-central1" + subnetwork = google_compute_subnetwork.main.self_link + keyring = "key-ring-test" + key = "key-test" + service_account_roles = ["roles/compute.imageUser", "roles/compute.networkUser"] +} diff --git a/test/fixtures/confidential_compute_instance/network.tf b/test/fixtures/confidential_compute_instance/network.tf new file mode 120000 index 00000000..98e7464a --- /dev/null +++ b/test/fixtures/confidential_compute_instance/network.tf @@ -0,0 +1 @@ +../shared/network.tf \ No newline at end of file diff --git a/test/fixtures/confidential_compute_instance/outputs.tf b/test/fixtures/confidential_compute_instance/outputs.tf new file mode 100644 index 00000000..bc49de48 --- /dev/null +++ b/test/fixtures/confidential_compute_instance/outputs.tf @@ -0,0 +1,40 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "self_link" { + description = "Self-link to the instance template." + value = module.confidential_computing.self_link +} + +output "name" { + description = "Name of the instance templates." + value = module.confidential_computing.name +} + +output "instance_self_link" { + description = "Self-link for compute instance" + value = module.confidential_computing.instance_self_link +} + +output "project_id" { + description = "The GCP project to use for integration tests." + value = var.project_id +} + +output "suffix" { + description = "Suffix used as an identifier for resources." + value = module.confidential_computing.suffix +} diff --git a/test/fixtures/confidential_compute_instance/variables.tf b/test/fixtures/confidential_compute_instance/variables.tf new file mode 100644 index 00000000..e232b248 --- /dev/null +++ b/test/fixtures/confidential_compute_instance/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The GCP project to use for integration tests." + type = string +} diff --git a/test/fixtures/confidential_compute_instance/versions.tf b/test/fixtures/confidential_compute_instance/versions.tf new file mode 100644 index 00000000..940b48d4 --- /dev/null +++ b/test/fixtures/confidential_compute_instance/versions.tf @@ -0,0 +1,19 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = ">=0.13" +} diff --git a/test/integration/confidential_compute_instance/confidential_compute_instance_test.go b/test/integration/confidential_compute_instance/confidential_compute_instance_test.go new file mode 100644 index 00000000..d36d255c --- /dev/null +++ b/test/integration/confidential_compute_instance/confidential_compute_instance_test.go @@ -0,0 +1,54 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package confidential_instance_template + +import ( + "fmt" + "testing" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/stretchr/testify/assert" +) + +func TestConfidentialInstanceTemplate(t *testing.T) { + const instanceNamePrefix = "confidential-encrypted-instance" + + confCompInst := tft.NewTFBlueprintTest(t) + confCompInst.DefineVerify(func(assert *assert.Assertions) { + confCompInst.DefaultVerify(assert) + projectId := confCompInst.GetStringOutput("project_id") + + computeInstanceList := gcloud.Run(t, fmt.Sprintf("compute instances list --format=json --project %s --filter name~%s", projectId, instanceNamePrefix)) + + assert.Len(computeInstanceList.Array(), 1) + computeInstance := computeInstanceList.Array()[0] + confidentialInstanceConfig := computeInstance.Get("confidentialInstanceConfig") + assert.True(confidentialInstanceConfig.Get("enableConfidentialCompute").Bool()) + assert.Equal("SEV", confidentialInstanceConfig.Get("confidentialInstanceType").String()) + assert.Equal("MIGRATE", computeInstance.Get("scheduling").Get("onHostMaintenance").String()) + serviceAccounts := computeInstance.Get("serviceAccounts").Array() + assert.Len(serviceAccounts, 1) + assert.Equal(fmt.Sprintf("confidential-compute-sa@%s.iam.gserviceaccount.com", projectId), serviceAccounts[0].Get("email").String()) + serviceAccountBindings := gcloud.Runf(t, "projects get-iam-policy %s --flatten bindings --filter bindings.members:'serviceAccount:%s' --format json", projectId, serviceAccounts[0].Get("email").String()).Array() + assert.Equal(2, len(serviceAccountBindings), "expect two bindings") + assert.ElementsMatch([]string{"roles/compute.imageUser", "roles/compute.networkUser"}, []string{serviceAccountBindings[0].Get("bindings.role").String(), serviceAccountBindings[1].Get("bindings.role").String()}) + disks := computeInstance.Get("disks").Array() + assert.Len(disks, 1) + defaultSuffix := confCompInst.GetStringOutput("suffix") + assert.Equal(fmt.Sprintf("projects/%s/locations/us/keyRings/key-ring-test-%s/cryptoKeys/key-test-%s/cryptoKeyVersions/1", projectId, defaultSuffix, defaultSuffix), disks[0].Get("diskEncryptionKey").Get("kmsKeyName").String()) + }) + confCompInst.Test() +}