diff --git a/examples/confidential_computing_intel/README.md b/examples/confidential_computing_intel/README.md new file mode 100644 index 00000000..c071a6dc --- /dev/null +++ b/examples/confidential_computing_intel/README.md @@ -0,0 +1,35 @@ +# confidential computing vm + +This is an example of a vm creation with confidential computing, +intel architecture, encrypted disk using a multiregion (US by default) +Cloud HSM key and a custom service account with cloud-platform scope. +It also creates org policies enforcing the use of CMEK encrypted instances +and confidential computing to all newly created VMs within the project. +Also, an additional org policy constraint is created, which only allows +Cloud KMS keys (used for CMEK protection) that come from the provided input project. +Note: existing VM instances won't be affected by the new org policy. + + +## 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_intel/main.tf b/examples/confidential_computing_intel/main.tf new file mode 100644 index 00000000..f8c90959 --- /dev/null +++ b/examples/confidential_computing_intel/main.tf @@ -0,0 +1,99 @@ +/** + * 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 = "3.0.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 = "terraform-google-modules/vm/google//modules/instance_template" + + region = var.region + project_id = var.project_id + subnetwork = var.subnetwork + + name_prefix = "confidential-intel-encrypted" + source_image_project = "tdx-guest-images" + source_image = "ubuntu-2204-lts" + disk_type = "pd-ssd" + machine_type = "c3-standard-4" + min_cpu_platform = "Intel Sapphire Rapids" + enable_confidential_vm = true + confidential_instance_type = "TDX" + + 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 = "~> 12.0" + + region = var.region + subnetwork = var.subnetwork + hostname = "confidential-intel-encrypted" + instance_template = module.instance_template.self_link + deletion_protection = false +} diff --git a/examples/confidential_computing_intel/org_policies.tf b/examples/confidential_computing_intel/org_policies.tf new file mode 100644 index 00000000..3c9ad5f4 --- /dev/null +++ b/examples/confidential_computing_intel/org_policies.tf @@ -0,0 +1,51 @@ +/** + * 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-org-policy" { + source = "terraform-google-modules/org-policy/google" + version = "~> 5.3" + + project_id = var.project_id + policy_for = "project" + constraint = "constraints/compute.restrictNonConfidentialComputing" + policy_type = "list" + deny = ["compute.googleapis.com"] + deny_list_length = 1 +} + +module "enforce-cmek-org-policy" { + source = "terraform-google-modules/org-policy/google" + version = "~> 5.3" + + project_id = var.project_id + policy_for = "project" + constraint = "constraints/gcp.restrictNonCmekServices" + policy_type = "list" + deny = ["compute.googleapis.com"] + deny_list_length = 1 +} + +module "restrict-cmek-cryptokey-projects-policy" { + source = "terraform-google-modules/org-policy/google" + version = "~> 5.3" + + project_id = var.project_id + policy_for = "project" + constraint = "constraints/gcp.restrictCmekCryptoKeyProjects" + policy_type = "list" + allow = ["projects/${var.project_id}"] + allow_list_length = 1 +} diff --git a/examples/confidential_computing_intel/outputs.tf b/examples/confidential_computing_intel/outputs.tf new file mode 100644 index 00000000..6bcf2e82 --- /dev/null +++ b/examples/confidential_computing_intel/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_intel/variables.tf b/examples/confidential_computing_intel/variables.tf new file mode 100644 index 00000000..6fe70f28 --- /dev/null +++ b/examples/confidential_computing_intel/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 b7ccf310..c7a0a690 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -52,6 +52,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/compute_disk_snapshot/metadata.yaml b/modules/compute_disk_snapshot/metadata.yaml index 9a9607e3..74bffae4 100644 --- a/modules/compute_disk_snapshot/metadata.yaml +++ b/modules/compute_disk_snapshot/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/compute_instance/metadata.yaml b/modules/compute_instance/metadata.yaml index 4c1adc2f..28efa5a3 100644 --- a/modules/compute_instance/metadata.yaml +++ b/modules/compute_instance/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/instance_template/metadata.yaml b/modules/instance_template/metadata.yaml index 5f50a426..666a02ee 100644 --- a/modules/instance_template/metadata.yaml +++ b/modules/instance_template/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/mig/metadata.yaml b/modules/mig/metadata.yaml index 476ed396..1f64aae0 100644 --- a/modules/mig/metadata.yaml +++ b/modules/mig/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/mig_with_percent/metadata.yaml b/modules/mig_with_percent/metadata.yaml index 9e7efaa6..9629799c 100644 --- a/modules/mig_with_percent/metadata.yaml +++ b/modules/mig_with_percent/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/preemptible_and_regular_instance_templates/metadata.yaml b/modules/preemptible_and_regular_instance_templates/metadata.yaml index 25f622a4..3a256cb2 100644 --- a/modules/preemptible_and_regular_instance_templates/metadata.yaml +++ b/modules/preemptible_and_regular_instance_templates/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/modules/umig/metadata.yaml b/modules/umig/metadata.yaml index 6ab20114..b2c055b3 100644 --- a/modules/umig/metadata.yaml +++ b/modules/umig/metadata.yaml @@ -42,6 +42,8 @@ spec: location: examples/confidential_computing - name: confidential_computing location: examples/instance_template/confidential_computing + - name: confidential_computing_intel + location: examples/confidential_computing_intel - name: disk_snapshot location: examples/compute_instance/disk_snapshot - name: encrypted_disks diff --git a/test/fixtures/confidential_intel_compute_instance/main.tf b/test/fixtures/confidential_intel_compute_instance/main.tf new file mode 100644 index 00000000..9ca70e10 --- /dev/null +++ b/test/fixtures/confidential_intel_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_intel" { + source = "../../../examples/confidential_computing_intel" + 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_intel_compute_instance/network.tf b/test/fixtures/confidential_intel_compute_instance/network.tf new file mode 120000 index 00000000..98e7464a --- /dev/null +++ b/test/fixtures/confidential_intel_compute_instance/network.tf @@ -0,0 +1 @@ +../shared/network.tf \ No newline at end of file diff --git a/test/fixtures/confidential_intel_compute_instance/outputs.tf b/test/fixtures/confidential_intel_compute_instance/outputs.tf new file mode 100644 index 00000000..1fcc0026 --- /dev/null +++ b/test/fixtures/confidential_intel_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_intel.self_link +} + +output "name" { + description = "Name of the instance templates." + value = module.confidential_computing_intel.name +} + +output "instance_self_link" { + description = "Self-link for compute instance." + value = module.confidential_computing_intel.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_intel.suffix +} diff --git a/test/fixtures/confidential_intel_compute_instance/variables.tf b/test/fixtures/confidential_intel_compute_instance/variables.tf new file mode 100644 index 00000000..e232b248 --- /dev/null +++ b/test/fixtures/confidential_intel_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_intel_compute_instance/versions.tf b/test/fixtures/confidential_intel_compute_instance/versions.tf new file mode 100644 index 00000000..940b48d4 --- /dev/null +++ b/test/fixtures/confidential_intel_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_intel_compute_instance/confidential_intel_compute_instance_test.go b/test/integration/confidential_intel_compute_instance/confidential_intel_compute_instance_test.go new file mode 100644 index 00000000..3c4250ec --- /dev/null +++ b/test/integration/confidential_intel_compute_instance/confidential_intel_compute_instance_test.go @@ -0,0 +1,70 @@ +// 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_intel_compute_instance + +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 TestConfidentialIntelComputeInstance(t *testing.T) { + const instanceNamePrefix = "confidential-intel-encrypted" + + 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("TDX", confidentialInstanceConfig.Get("confidentialInstanceType").String()) + assert.Equal("TERMINATE", 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()) + + org_policy_cmek_constraint := gcloud.Runf(t, "resource-manager org-policies list --project=%s --format=json --filter constraint='constraints/gcp.restrictNonCmekServices'", projectId).Array() + assert.Len(org_policy_cmek_constraint, 1) + cmek_denied_values_list := org_policy_cmek_constraint[0].Get("listPolicy.deniedValues").Array() + assert.Len(cmek_denied_values_list, 1) + assert.Equal("compute.googleapis.com", cmek_denied_values_list[0].String()) + org_policy_cmek_projects := gcloud.Runf(t, "resource-manager org-policies list --project=%s --format=json --filter constraint='constraints/gcp.restrictCmekCryptoKeyProjects'", projectId).Array() + assert.Len(org_policy_cmek_projects, 1) + cmek_allowed_projects := org_policy_cmek_projects[0].Get("listPolicy.allowedValues").Array() + assert.Len(cmek_allowed_projects, 1) + assert.Equal(fmt.Sprintf("projects/%s", projectId), cmek_allowed_projects[0].String()) + org_policy_confidential_constraint := gcloud.Runf(t, "resource-manager org-policies list --project=%s --format=json --filter constraint='constraints/compute.restrictNonConfidentialComputing'", projectId).Array() + assert.Len(org_policy_confidential_constraint, 1) + cc_denied_values_list := org_policy_confidential_constraint[0].Get("listPolicy.deniedValues").Array() + assert.Len(cc_denied_values_list, 1) + assert.Equal("compute.googleapis.com", cc_denied_values_list[0].String()) + }) + confCompInst.Test() +}