Repository: kubernetes-sigs/cluster-api-provider-gcp
Pull request #809 (2023-01-18 12:09:05)

Add support for confidential compute
/kind feature

**What this PR does / why we need it**:
The PR adds support for enabling confidential compute service as well as setting the instances on host maintenance policy.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #
https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues/804

**Special notes for your reviewer**:

_Please confirm that if this PR changes any image versions, then that's the sole change this PR makes._

**TODOs**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR. -->

- [x] squashed commits
- [ ] includes documentation
- [x] adds unit tests

**Release note**:
-->
```release-note
Add confidential compute  to API
Add on host maintenance  to API
```


k8s-ci-robot: Hi @eranco74. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-gcp).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

richardcase: /ok-to-test
richardcase: @eranco74 - thanks for this. Does the current CAPI image satisfy the [requirements](url) for the guest OS? 
eranco74: > @eranco74 - thanks for this. Does the current CAPI image satisfy the [requirements](url) for the guest OS?
Yes, it has all the required guest-os-feature tags:
```
gcloud compute images describe projects/k8s-staging-cluster-api-gcp/global/images/cluster-api-ubuntu-2004-v1-25-4-nightly
architecture: X86_64
archiveSizeBytes: '2495363520'
creationTimestamp: '2022-12-27T08:21:26.569-08:00'
description: Created by Packer
diskSizeGb: '20'
family: capi-ubuntu-2004-k8s-v1-25
guestOsFeatures:
- type: VIRTIO_SCSI_MULTIQUEUE
- type: SEV_CAPABLE
- type: UEFI_COMPATIBLE
- type: GVNIC
```
k8s-ci-robot: @eranco74: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-gcp-verify | cc4f2637612232f12b1e18fb26a9f500544ee227 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/809/pull-cluster-api-provider-gcp-verify/1618319461041836032) | true | `/test pull-cluster-api-provider-gcp-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-gcp&pr=809). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Aeranco74). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
richardcase: @eranco74 - it's probably worth creating an issue to follow this up with updating the e2e tests to cover confidential compute. wdyt?
eranco74: > @eranco74 - it's probably worth creating an issue to follow this up with updating the e2e tests to cover confidential compute. wdyt?

https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues/813
k8s-ci-robot: @eranco74: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-gcp-verify | c37a092ed1dfcc14b47fcc04c3041ffec7dffe66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/809/pull-cluster-api-provider-gcp-verify/1618594754541391872) | true | `/test pull-cluster-api-provider-gcp-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-gcp&pr=809). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Aeranco74). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @eranco74: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-gcp-verify | c37a092ed1dfcc14b47fcc04c3041ffec7dffe66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/809/pull-cluster-api-provider-gcp-verify/1618594754541391872) | true | `/test pull-cluster-api-provider-gcp-verify`
pull-cluster-api-provider-gcp-apidiff | c37a092ed1dfcc14b47fcc04c3041ffec7dffe66 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/809/pull-cluster-api-provider-gcp-apidiff/1618594754650443776) | false | `/test pull-cluster-api-provider-gcp-apidiff`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-gcp&pr=809). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Aeranco74). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/api/v1alpha3/gcpmachine_conversion.go b/api/v1alpha3/gcpmachine_conversion.go
index f4cdb002..3a064aa6 100644
--- a/api/v1alpha3/gcpmachine_conversion.go
+++ b/api/v1alpha3/gcpmachine_conversion.go
@@ -45,6 +45,14 @@ func (src *GCPMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint
 		dst.Spec.ShieldedInstanceConfig = restored.Spec.ShieldedInstanceConfig
 	}
 
+	if restored.Spec.OnHostMaintenance != nil {
+		dst.Spec.OnHostMaintenance = restored.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.ConfidentialCompute != nil {
+		dst.Spec.ConfidentialCompute = restored.Spec.ConfidentialCompute
+	}
+
 	return nil
 }
 
diff --git a/api/v1alpha3/gcpmachinetemplate_conversion.go b/api/v1alpha3/gcpmachinetemplate_conversion.go
index 5df207d8..738cb6df 100644
--- a/api/v1alpha3/gcpmachinetemplate_conversion.go
+++ b/api/v1alpha3/gcpmachinetemplate_conversion.go
@@ -45,6 +45,14 @@ func (src *GCPMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolin
 		dst.Spec.Template.Spec.ShieldedInstanceConfig = restored.Spec.Template.Spec.ShieldedInstanceConfig
 	}
 
+	if restored.Spec.Template.Spec.OnHostMaintenance != nil {
+		dst.Spec.Template.Spec.OnHostMaintenance = restored.Spec.Template.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.Template.Spec.ConfidentialCompute != nil {
+		dst.Spec.Template.Spec.ConfidentialCompute = restored.Spec.Template.Spec.ConfidentialCompute
+	}
+
 	return nil
 }
 
diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go
index fcc677c8..92146887 100644
--- a/api/v1alpha3/zz_generated.conversion.go
+++ b/api/v1alpha3/zz_generated.conversion.go
@@ -574,6 +574,8 @@ func autoConvert_v1beta1_GCPMachineSpec_To_v1alpha3_GCPMachineSpec(in *v1beta1.G
 	out.Preemptible = in.Preemptible
 	// WARNING: in.IPForwarding requires manual conversion: does not exist in peer-type
 	// WARNING: in.ShieldedInstanceConfig requires manual conversion: does not exist in peer-type
+	// WARNING: in.OnHostMaintenance requires manual conversion: does not exist in peer-type
+	// WARNING: in.ConfidentialCompute requires manual conversion: does not exist in peer-type
 	return nil
 }
 
diff --git a/api/v1alpha4/gcpmachine_conversion.go b/api/v1alpha4/gcpmachine_conversion.go
index 3eee5dfd..3388f94b 100644
--- a/api/v1alpha4/gcpmachine_conversion.go
+++ b/api/v1alpha4/gcpmachine_conversion.go
@@ -45,6 +45,14 @@ func (src *GCPMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint
 		dst.Spec.ShieldedInstanceConfig = restored.Spec.ShieldedInstanceConfig
 	}
 
+	if restored.Spec.OnHostMaintenance != nil {
+		dst.Spec.OnHostMaintenance = restored.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.ConfidentialCompute != nil {
+		dst.Spec.ConfidentialCompute = restored.Spec.ConfidentialCompute
+	}
+
 	return nil
 }
 
diff --git a/api/v1alpha4/gcpmachinetemplate_conversion.go b/api/v1alpha4/gcpmachinetemplate_conversion.go
index d618e2fe..2c5fa5e2 100644
--- a/api/v1alpha4/gcpmachinetemplate_conversion.go
+++ b/api/v1alpha4/gcpmachinetemplate_conversion.go
@@ -46,6 +46,13 @@ func (src *GCPMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolin
 	if restored.Spec.Template.Spec.ShieldedInstanceConfig != nil {
 		dst.Spec.Template.Spec.ShieldedInstanceConfig = restored.Spec.Template.Spec.ShieldedInstanceConfig
 	}
+	if restored.Spec.Template.Spec.OnHostMaintenance != nil {
+		dst.Spec.Template.Spec.OnHostMaintenance = restored.Spec.Template.Spec.OnHostMaintenance
+	}
+
+	if restored.Spec.Template.Spec.ConfidentialCompute != nil {
+		dst.Spec.Template.Spec.ConfidentialCompute = restored.Spec.Template.Spec.ConfidentialCompute
+	}
 
 	return nil
 }
diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go
index d64771f7..3d6b0f61 100644
--- a/api/v1alpha4/zz_generated.conversion.go
+++ b/api/v1alpha4/zz_generated.conversion.go
@@ -741,6 +741,8 @@ func autoConvert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(in *v1beta1.G
 	out.Preemptible = in.Preemptible
 	// WARNING: in.IPForwarding requires manual conversion: does not exist in peer-type
 	// WARNING: in.ShieldedInstanceConfig requires manual conversion: does not exist in peer-type
+	// WARNING: in.OnHostMaintenance requires manual conversion: does not exist in peer-type
+	// WARNING: in.ConfidentialCompute requires manual conversion: does not exist in peer-type
 	return nil
 }
 
diff --git a/api/v1beta1/gcpmachine_types.go b/api/v1beta1/gcpmachine_types.go
index 95471722..4fff0c10 100644
--- a/api/v1beta1/gcpmachine_types.go
+++ b/api/v1beta1/gcpmachine_types.go
@@ -122,6 +122,30 @@ type GCPShieldedInstanceConfig struct {
 	IntegrityMonitoring IntegrityMonitoringPolicy `json:"integrityMonitoring,omitempty"`
 }
 
+// ConfidentialComputePolicy represents the confidential compute configuration for the GCP machine.
+type ConfidentialComputePolicy string
+
+const (
+	// ConfidentialComputePolicyEnabled enables confidential compute for the GCP machine.
+	ConfidentialComputePolicyEnabled ConfidentialComputePolicy = "Enabled"
+	// ConfidentialComputePolicyDisabled disables confidential compute for the GCP machine.
+	ConfidentialComputePolicyDisabled ConfidentialComputePolicy = "Disabled"
+)
+
+// Confidential VM supports Compute Engine machine types in the following series:
+// reference: https://cloud.google.com/compute/confidential-vm/docs/os-and-machine-type#machine-type
+var confidentialComputeSupportedMachineSeries = []string{"n2d", "c2d"}
+
+// HostMaintenancePolicy represents the desired behavior ase of a host maintenance event.
+type HostMaintenancePolicy string
+
+const (
+	// HostMaintenancePolicyMigrate causes Compute Engine to live migrate an instance when there is a maintenance event.
+	HostMaintenancePolicyMigrate HostMaintenancePolicy = "Migrate"
+	// HostMaintenancePolicyTerminate - stops an instance instead of migrating it.
+	HostMaintenancePolicyTerminate HostMaintenancePolicy = "Terminate"
+)
+
 // GCPMachineSpec defines the desired state of GCPMachine.
 type GCPMachineSpec struct {
 	// InstanceType is the type of instance to create. Example: n1.standard-2
@@ -209,6 +233,19 @@ type GCPMachineSpec struct {
 	// ShieldedInstanceConfig is the Shielded VM configuration for this machine
 	// +optional
 	ShieldedInstanceConfig *GCPShieldedInstanceConfig `json:"shieldedInstanceConfig,omitempty"`
+
+	// OnHostMaintenance determines the behavior when a maintenance event occurs that might cause the instance to reboot.
+	// If omitted, the platform chooses a default, which is subject to change over time, currently that default is "Migrate".
+	// +kubebuilder:validation:Enum=Migrate;Terminate;
+	// +optional
+	OnHostMaintenance *HostMaintenancePolicy `json:"onHostMaintenance,omitempty"`
+
+	// ConfidentialCompute Defines whether the instance should have confidential compute enabled.
+	// If enabled OnHostMaintenance is required to be set to "Terminate".
+	// If omitted, the platform chooses a default, which is subject to change over time, currently that default is false.
+	// +kubebuilder:validation:Enum=Enabled;Disabled
+	// +optional
+	ConfidentialCompute *ConfidentialComputePolicy `json:"confidentialCompute,omitempty"`
 }
 
 // MetadataItem defines a single piece of metadata associated with an instance.
diff --git a/api/v1beta1/gcpmachine_webhook.go b/api/v1beta1/gcpmachine_webhook.go
index 65f5c743..a65567a8 100644
--- a/api/v1beta1/gcpmachine_webhook.go
+++ b/api/v1beta1/gcpmachine_webhook.go
@@ -17,7 +17,11 @@ limitations under the License.
 package v1beta1
 
 import (
+	"fmt"
 	"reflect"
+	"strings"
+
+	"k8s.io/utils/strings/slices"
 
 	"github.com/pkg/errors"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -45,8 +49,7 @@ var _ webhook.Validator = &GCPMachine{}
 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
 func (m *GCPMachine) ValidateCreate() error {
 	clusterlog.Info("validate create", "name", m.Name)
-
-	return nil
+	return validateConfidentialCompute(m.Spec)
 }
 
 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
@@ -99,3 +102,17 @@ func (m *GCPMachine) ValidateDelete() error {
 func (m *GCPMachine) Default() {
 	clusterlog.Info("default", "name", m.Name)
 }
+
+func validateConfidentialCompute(spec GCPMachineSpec) error {
+	if spec.ConfidentialCompute != nil && *spec.ConfidentialCompute == ConfidentialComputePolicyEnabled {
+		if spec.OnHostMaintenance == nil || *spec.OnHostMaintenance == HostMaintenancePolicyMigrate {
+			return fmt.Errorf("ConfidentialCompute require OnHostMaintenance to be set to %s, the current value is: %s", HostMaintenancePolicyTerminate, HostMaintenancePolicyMigrate)
+		}
+
+		machineSeries := strings.Split(spec.InstanceType, "-")[0]
+		if !slices.Contains(confidentialComputeSupportedMachineSeries, machineSeries) {
+			return fmt.Errorf("ConfidentialCompute require instance type in the following series: %s", confidentialComputeSupportedMachineSeries)
+		}
+	}
+	return nil
+}
diff --git a/api/v1beta1/gcpmachinetemplate_webhook.go b/api/v1beta1/gcpmachinetemplate_webhook.go
index 971deeff..b8d6f486 100644
--- a/api/v1beta1/gcpmachinetemplate_webhook.go
+++ b/api/v1beta1/gcpmachinetemplate_webhook.go
@@ -46,7 +46,7 @@ var _ webhook.Validator = &GCPMachineTemplate{}
 func (r *GCPMachineTemplate) ValidateCreate() error {
 	clusterlog.Info("validate create", "name", r.Name)
 
-	return nil
+	return validateConfidentialCompute(r.Spec.Template.Spec)
 }
 
 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 97c66e85..21c85dce 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -440,6 +440,16 @@ func (in *GCPMachineSpec) DeepCopyInto(out *GCPMachineSpec) {
 		*out = new(GCPShieldedInstanceConfig)
 		**out = **in
 	}
+	if in.OnHostMaintenance != nil {
+		in, out := &in.OnHostMaintenance, &out.OnHostMaintenance
+		*out = new(HostMaintenancePolicy)
+		**out = **in
+	}
+	if in.ConfidentialCompute != nil {
+		in, out := &in.ConfidentialCompute, &out.ConfidentialCompute
+		*out = new(ConfidentialComputePolicy)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineSpec.
diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go
index 2cdfa1fa..209e52c0 100644
--- a/cloud/scope/machine.go
+++ b/cloud/scope/machine.go
@@ -24,6 +24,8 @@ import (
 	"sort"
 	"strings"
 
+	"github.com/go-logr/logr"
+
 	"github.com/pkg/errors"
 	"golang.org/x/mod/semver"
 	"google.golang.org/api/compute/v1"
@@ -323,7 +325,7 @@ func (m *MachineScope) InstanceAdditionalMetadataSpec() *compute.Metadata {
 }
 
 // InstanceSpec returns instance spec.
-func (m *MachineScope) InstanceSpec() *compute.Instance {
+func (m *MachineScope) InstanceSpec(log logr.Logger) *compute.Instance {
 	instance := &compute.Instance{
 		Name:        m.Name(),
 		Zone:        m.Zone(),
@@ -367,6 +369,24 @@ func (m *MachineScope) InstanceSpec() *compute.Instance {
 			instance.ShieldedInstanceConfig.EnableIntegrityMonitoring = false
 		}
 	}
+	if m.GCPMachine.Spec.OnHostMaintenance != nil {
+		switch *m.GCPMachine.Spec.OnHostMaintenance {
+		case infrav1.HostMaintenancePolicyMigrate:
+			instance.Scheduling.OnHostMaintenance = "MIGRATE"
+		case infrav1.HostMaintenancePolicyTerminate:
+			instance.Scheduling.OnHostMaintenance = "TERMINATE"
+		default:
+			log.Error(errors.New("Invalid value"), "Unknown OnHostMaintenance value", "Spec.OnHostMaintenance", *m.GCPMachine.Spec.OnHostMaintenance)
+		}
+
+		instance.Scheduling.OnHostMaintenance = strings.ToUpper(string(*m.GCPMachine.Spec.OnHostMaintenance))
+	}
+	if m.GCPMachine.Spec.ConfidentialCompute != nil {
+		enabled := *m.GCPMachine.Spec.ConfidentialCompute == infrav1.ConfidentialComputePolicyEnabled
+		instance.ConfidentialInstanceConfig = &compute.ConfidentialInstanceConfig{
+			EnableConfidentialCompute: enabled,
+		}
+	}
 
 	instance.Disks = append(instance.Disks, m.InstanceImageSpec())
 	instance.Disks = append(instance.Disks, m.InstanceAdditionalDiskSpec()...)
diff --git a/cloud/services/compute/instances/reconcile.go b/cloud/services/compute/instances/reconcile.go
index 0a3df681..55abcd46 100644
--- a/cloud/services/compute/instances/reconcile.go
+++ b/cloud/services/compute/instances/reconcile.go
@@ -98,7 +98,7 @@ func (s *Service) Reconcile(ctx context.Context) error {
 func (s *Service) Delete(ctx context.Context) error {
 	log := log.FromContext(ctx)
 	log.Info("Deleting instance resources")
-	instanceSpec := s.scope.InstanceSpec()
+	instanceSpec := s.scope.InstanceSpec(log)
 	instanceName := instanceSpec.Name
 	instanceKey := meta.ZonalKey(instanceName, s.scope.Zone())
 	log.V(2).Info("Looking for instance before deleting", "name", instanceName, "zone", s.scope.Zone())
@@ -131,7 +131,7 @@ func (s *Service) createOrGetInstance(ctx context.Context) (*compute.Instance, e
 		return nil, errors.Wrap(err, "failed to retrieve bootstrap data")
 	}
 
-	instanceSpec := s.scope.InstanceSpec()
+	instanceSpec := s.scope.InstanceSpec(log)
 	instanceName := instanceSpec.Name
 	instanceKey := meta.ZonalKey(instanceName, s.scope.Zone())
 	instanceSpec.Metadata.Items = append(instanceSpec.Metadata.Items, &compute.MetadataItems{
diff --git a/cloud/services/compute/instances/service.go b/cloud/services/compute/instances/service.go
index 942bad46..b8ec3a2a 100644
--- a/cloud/services/compute/instances/service.go
+++ b/cloud/services/compute/instances/service.go
@@ -19,6 +19,8 @@ package instances
 import (
 	"context"
 
+	"github.com/go-logr/logr"
+
 	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/filter"
 	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
 	"google.golang.org/api/compute/v1"
@@ -40,7 +42,7 @@ type instancegroupsInterface interface {
 // Scope is an interfaces that hold used methods.
 type Scope interface {
 	cloud.Machine
-	InstanceSpec() *compute.Instance
+	InstanceSpec(log logr.Logger) *compute.Instance
 	InstanceImageSpec() *compute.AttachedDisk
 	InstanceAdditionalDiskSpec() []*compute.AttachedDisk
 }
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
index ab9ab9de..71236d23 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
@@ -548,6 +548,16 @@ spec:
                 items:
                   type: string
                 type: array
+              confidentialCompute:
+                description: ConfidentialCompute Defines whether the instance should
+                  have confidential compute enabled. If enabled OnHostMaintenance
+                  is required to be set to "Terminate". If omitted, the platform chooses
+                  a default, which is subject to change over time, currently that
+                  default is false.
+                enum:
+                - Enabled
+                - Disabled
+                type: string
               image:
                 description: Image is the full reference to a valid image to be used
                   for this machine. Takes precedence over ImageFamily.
@@ -570,6 +580,15 @@ spec:
                 - Enabled
                 - Disabled
                 type: string
+              onHostMaintenance:
+                description: OnHostMaintenance determines the behavior when a maintenance
+                  event occurs that might cause the instance to reboot. If omitted,
+                  the platform chooses a default, which is subject to change over
+                  time, currently that default is "Migrate".
+                enum:
+                - Migrate
+                - Terminate
+                type: string
               preemptible:
                 description: Preemptible defines if instance is preemptible
                 type: boolean
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
index 86cd02b1..1de222cd 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
@@ -439,6 +439,16 @@ spec:
                         items:
                           type: string
                         type: array
+                      confidentialCompute:
+                        description: ConfidentialCompute Defines whether the instance
+                          should have confidential compute enabled. If enabled OnHostMaintenance
+                          is required to be set to "Terminate". If omitted, the platform
+                          chooses a default, which is subject to change over time,
+                          currently that default is false.
+                        enum:
+                        - Enabled
+                        - Disabled
+                        type: string
                       image:
                         description: Image is the full reference to a valid image
                           to be used for this machine. Takes precedence over ImageFamily.
@@ -461,6 +471,16 @@ spec:
                         - Enabled
                         - Disabled
                         type: string
+                      onHostMaintenance:
+                        description: OnHostMaintenance determines the behavior when
+                          a maintenance event occurs that might cause the instance
+                          to reboot. If omitted, the platform chooses a default, which
+                          is subject to change over time, currently that default is
+                          "Migrate".
+                        enum:
+                        - Migrate
+                        - Terminate
+                        type: string
                       preemptible:
                         description: Preemptible defines if instance is preemptible
                         type: boolean
diff --git a/go.mod b/go.mod
index 53bfccf5..d74eade2 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
 	cloud.google.com/go/container v1.7.0
 	cloud.google.com/go/iam v0.8.0
 	github.com/GoogleCloudPlatform/k8s-cloud-provider v1.20.0
+	github.com/go-logr/logr v1.2.3
 	github.com/google/go-cmp v0.5.9
 	github.com/googleapis/gax-go/v2 v2.7.0
 	github.com/onsi/ginkgo v1.16.5
@@ -58,7 +59,6 @@ require (
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
-	github.com/go-logr/logr v1.2.3 // indirect
 	github.com/go-logr/zapr v1.2.3 // indirect
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
 	github.com/go-openapi/jsonreference v0.20.0 // indirect


Test patch:
diff --git a/api/v1beta1/gcpmachine_webhook_test.go b/api/v1beta1/gcpmachine_webhook_test.go
new file mode 100644
index 00000000..d597c514
--- /dev/null
+++ b/api/v1beta1/gcpmachine_webhook_test.go
@@ -0,0 +1,101 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+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 v1beta1
+
+import (
+	"testing"
+
+	. "github.com/onsi/gomega"
+)
+
+func TestGCPMachine_ValidateCreate(t *testing.T) {
+	g := NewWithT(t)
+	confidentialComputeEnabled := ConfidentialComputePolicyEnabled
+	onHostMaintenanceTerminate := HostMaintenancePolicyTerminate
+	onHostMaintenanceMigrate := HostMaintenancePolicyMigrate
+	tests := []struct {
+		name string
+		*GCPMachine
+		wantErr bool
+	}{
+		{
+			name: "GCPMachined with OnHostMaintenance set to Terminate - valid",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					InstanceType:      "n2d-standard-4",
+					OnHostMaintenance: &onHostMaintenanceTerminate,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachined with ConfidentialCompute enabled and OnHostMaintenance set to Terminate - valid",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					InstanceType:        "n2d-standard-4",
+					OnHostMaintenance:   &onHostMaintenanceTerminate,
+					ConfidentialCompute: &confidentialComputeEnabled,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachined with ConfidentialCompute enabled and OnHostMaintenance set to Migrate - invalid",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					InstanceType:        "n2d-standard-4",
+					OnHostMaintenance:   &onHostMaintenanceMigrate,
+					ConfidentialCompute: &confidentialComputeEnabled,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachined with ConfidentialCompute enabled and default OnHostMaintenance (Migrate) - invalid",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					InstanceType:        "n2d-standard-4",
+					ConfidentialCompute: &confidentialComputeEnabled,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachined with ConfidentialCompute enabled and unsupported instance type - invalid",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					InstanceType:        "e2-standard-4",
+					ConfidentialCompute: &confidentialComputeEnabled,
+					OnHostMaintenance:   &onHostMaintenanceTerminate,
+				},
+			},
+			wantErr: true,
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			err := test.GCPMachine.ValidateCreate()
+			if test.wantErr {
+				g.Expect(err).To(HaveOccurred())
+			} else {
+				g.Expect(err).NotTo(HaveOccurred())
+			}
+		})
+	}
+}
diff --git a/api/v1beta1/gcpmachinetemplate_webhook_test.go b/api/v1beta1/gcpmachinetemplate_webhook_test.go
new file mode 100644
index 00000000..5f1d4368
--- /dev/null
+++ b/api/v1beta1/gcpmachinetemplate_webhook_test.go
@@ -0,0 +1,116 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+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 v1beta1
+
+import (
+	"testing"
+
+	. "github.com/onsi/gomega"
+)
+
+func TestGCPMachineTemplate_ValidateCreate(t *testing.T) {
+	g := NewWithT(t)
+	confidentialComputeEnabled := ConfidentialComputePolicyEnabled
+	onHostMaintenanceTerminate := HostMaintenancePolicyTerminate
+	onHostMaintenanceMigrate := HostMaintenancePolicyMigrate
+	tests := []struct {
+		name     string
+		template *GCPMachineTemplate
+		wantErr  bool
+	}{
+		{
+			name: "GCPMachineTemplate with OnHostMaintenance set to Terminate - valid",
+			template: &GCPMachineTemplate{
+				Spec: GCPMachineTemplateSpec{
+					Template: GCPMachineTemplateResource{
+						Spec: GCPMachineSpec{
+							InstanceType:      "n2d-standard-4",
+							OnHostMaintenance: &onHostMaintenanceTerminate,
+						}},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachineTemplate with ConfidentialCompute enabled and OnHostMaintenance set to Terminate - valid",
+			template: &GCPMachineTemplate{
+				Spec: GCPMachineTemplateSpec{
+					Template: GCPMachineTemplateResource{
+						Spec: GCPMachineSpec{
+							InstanceType:        "n2d-standard-4",
+							ConfidentialCompute: &confidentialComputeEnabled,
+							OnHostMaintenance:   &onHostMaintenanceTerminate,
+						}},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachineTemplate with ConfidentialCompute enabled and OnHostMaintenance set to Migrate - invalid",
+			template: &GCPMachineTemplate{
+				Spec: GCPMachineTemplateSpec{
+					Template: GCPMachineTemplateResource{
+						Spec: GCPMachineSpec{
+							InstanceType:        "n2d-standard-4",
+							ConfidentialCompute: &confidentialComputeEnabled,
+							OnHostMaintenance:   &onHostMaintenanceMigrate,
+						}},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachineTemplate with ConfidentialCompute enabled and default OnHostMaintenance (Migrate) - invalid",
+			template: &GCPMachineTemplate{
+				Spec: GCPMachineTemplateSpec{
+					Template: GCPMachineTemplateResource{
+						Spec: GCPMachineSpec{
+							InstanceType:        "n2d-standard-4",
+							ConfidentialCompute: &confidentialComputeEnabled,
+						}},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachineTemplate with ConfidentialCompute enabled and unsupported instance type - invalid",
+			template: &GCPMachineTemplate{
+				Spec: GCPMachineTemplateSpec{
+					Template: GCPMachineTemplateResource{
+						Spec: GCPMachineSpec{
+							InstanceType:        "e2-standard-4",
+							ConfidentialCompute: &confidentialComputeEnabled,
+							OnHostMaintenance:   &onHostMaintenanceTerminate,
+						}},
+				},
+			},
+			wantErr: true,
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			err := test.template.ValidateCreate()
+			if test.wantErr {
+				g.Expect(err).To(HaveOccurred())
+			} else {
+				g.Expect(err).NotTo(HaveOccurred())
+			}
+		})
+	}
+}
diff --git a/cloud/services/compute/instances/reconcile_test.go b/cloud/services/compute/instances/reconcile_test.go
index 0212bf62..3e3bf734 100644
--- a/cloud/services/compute/instances/reconcile_test.go
+++ b/cloud/services/compute/instances/reconcile_test.go
@@ -19,6 +19,7 @@ package instances
 import (
 	"context"
 	"net/http"
+	"strings"
 	"testing"
 
 	"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
@@ -395,6 +396,137 @@ func TestService_createOrGetInstance(t *testing.T) {
 				Zone: "us-central1-c",
 			},
 		},
+		{
+			name: "instance does not exist (should create instance) with confidential compute enabled and TERMINATE OnHostMaintenance",
+			scope: func() Scope {
+				machineScope.GCPMachine = getFakeGCPMachine()
+				confidentialComputePolicyEnabled := infrav1.ConfidentialComputePolicyEnabled
+				machineScope.GCPMachine.Spec.ConfidentialCompute = &confidentialComputePolicyEnabled
+				hostMaintenancePolicyTerminate := infrav1.HostMaintenancePolicyTerminate
+				machineScope.GCPMachine.Spec.OnHostMaintenance = &hostMaintenancePolicyTerminate
+				return machineScope
+			},
+			mockInstance: &cloud.MockInstances{
+				ProjectRouter: &cloud.SingleProjectRouter{ID: "proj-id"},
+				Objects:       map[meta.Key]*cloud.MockInstancesObj{},
+			},
+			want: &compute.Instance{
+				Name:         "my-machine",
+				CanIpForward: true,
+				Disks: []*compute.AttachedDisk{
+					{
+						AutoDelete: true,
+						Boot:       true,
+						InitializeParams: &compute.AttachedDiskInitializeParams{
+							DiskType:    "zones/us-central1-c/diskTypes/pd-standard",
+							SourceImage: "projects/my-proj/global/images/family/capi-ubuntu-1804-k8s-v1-19",
+						},
+					},
+				},
+				Labels: map[string]string{
+					"capg-role":               "node",
+					"capg-cluster-my-cluster": "owned",
+					"foo":                     "bar",
+				},
+				MachineType: "zones/us-central1-c/machineTypes",
+				Metadata: &compute.Metadata{
+					Items: []*compute.MetadataItems{
+						{
+							Key:   "user-data",
+							Value: pointer.String("Zm9vCg=="),
+						},
+					},
+				},
+				NetworkInterfaces: []*compute.NetworkInterface{
+					{
+						Network: "projects/my-proj/global/networks/default",
+					},
+				},
+				SelfLink: "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
+				Scheduling: &compute.Scheduling{
+					OnHostMaintenance: strings.ToUpper(string(infrav1.HostMaintenancePolicyTerminate)),
+				},
+				ConfidentialInstanceConfig: &compute.ConfidentialInstanceConfig{
+					EnableConfidentialCompute: true,
+				},
+				ServiceAccounts: []*compute.ServiceAccount{
+					{
+						Email:  "default",
+						Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
+					},
+				},
+				Tags: &compute.Tags{
+					Items: []string{
+						"my-cluster-node",
+						"my-cluster",
+					},
+				},
+				Zone: "us-central1-c",
+			},
+		},
+		{
+			name: "instance does not exist (should create instance) with MIGRATE OnHostMaintenance",
+			scope: func() Scope {
+				machineScope.GCPMachine = getFakeGCPMachine()
+				hostMaintenancePolicyTerminate := infrav1.HostMaintenancePolicyMigrate
+				machineScope.GCPMachine.Spec.OnHostMaintenance = &hostMaintenancePolicyTerminate
+				return machineScope
+			},
+			mockInstance: &cloud.MockInstances{
+				ProjectRouter: &cloud.SingleProjectRouter{ID: "proj-id"},
+				Objects:       map[meta.Key]*cloud.MockInstancesObj{},
+			},
+			want: &compute.Instance{
+				Name:         "my-machine",
+				CanIpForward: true,
+				Disks: []*compute.AttachedDisk{
+					{
+						AutoDelete: true,
+						Boot:       true,
+						InitializeParams: &compute.AttachedDiskInitializeParams{
+							DiskType:    "zones/us-central1-c/diskTypes/pd-standard",
+							SourceImage: "projects/my-proj/global/images/family/capi-ubuntu-1804-k8s-v1-19",
+						},
+					},
+				},
+				Labels: map[string]string{
+					"capg-role":               "node",
+					"capg-cluster-my-cluster": "owned",
+					"foo":                     "bar",
+				},
+				MachineType: "zones/us-central1-c/machineTypes",
+				Metadata: &compute.Metadata{
+					Items: []*compute.MetadataItems{
+						{
+							Key:   "user-data",
+							Value: pointer.String("Zm9vCg=="),
+						},
+					},
+				},
+				NetworkInterfaces: []*compute.NetworkInterface{
+					{
+						Network: "projects/my-proj/global/networks/default",
+					},
+				},
+				SelfLink: "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
+				Scheduling: &compute.Scheduling{
+					OnHostMaintenance: strings.ToUpper(string(infrav1.HostMaintenancePolicyMigrate)),
+				},
+				ServiceAccounts: []*compute.ServiceAccount{
+					{
+						Email:  "default",
+						Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
+					},
+				},
+				Tags: &compute.Tags{
+					Items: []string{
+						"my-cluster-node",
+						"my-cluster",
+					},
+				},
+				Zone: "us-central1-c",
+			},
+		},
 		{
 			name:  "FailureDomain not given (should pick up a failure domain from the cluster)",
 			scope: func() Scope { return machineScopeWithoutFailureDomain },