Repository: kubernetes-sigs/cluster-api-provider-gcp
Pull request #1137 (2024-02-09 12:32:48)

Add support for disk encryption key in GCPMachine
 <!-- If this is your first PR, welcome! Please make sure you read the [contributing guidelines](../CONTRIBUTING.md). -->

 <!-- Please label this pull request according to what type of issue you are addressing (see ../CONTRIBUTING.md) -->
**What type of PR is this?**

<!--
Add one of the following kinds:
/kind feature

-->

**What this PR does / why we need it**:

Add support for the disk encryption key for the boot disk and and additional disks in the GCP machine.

**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 #

**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**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
Add support for disk encryption key in GCPMachine
```


k8s-ci-robot: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<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>
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1137#" title="Author self-approved">bfournie</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [sbueringer](https://github.com/sbueringer) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-gcp).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["sbueringer"]} -->
k8s-ci-robot: Hi @bfournie. 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>

k8s-ci-robot: Welcome @bfournie! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/cluster-api-provider-gcp'>kubernetes-sigs/cluster-api-provider-gcp</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/cluster-api-provider-gcp has [its own contribution guidelines](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
damdo: /ok-to-test
/assign @cpanato @richardcase 
cpanato: /test ls
k8s-ci-robot: @cpanato: The specified target(s) for `/test` were not found.
The following commands are available to trigger required jobs:
* `/test pull-cluster-api-provider-gcp-build`
* `/test pull-cluster-api-provider-gcp-e2e-test`
* `/test pull-cluster-api-provider-gcp-make`
* `/test pull-cluster-api-provider-gcp-test`
* `/test pull-cluster-api-provider-gcp-verify`

The following commands are available to trigger optional jobs:
* `/test pull-cluster-api-provider-gcp-apidiff`
* `/test pull-cluster-api-provider-gcp-capi-e2e`
* `/test pull-cluster-api-provider-gcp-conformance`
* `/test pull-cluster-api-provider-gcp-conformance-ci-artifacts`
* `/test pull-cluster-api-provider-gcp-coverage`
* `/test pull-cluster-api-provider-gcp-coverage-release-1-4`
* `/test pull-cluster-api-provider-gcp-coverage-release-1-5`
* `/test pull-cluster-api-provider-gcp-e2e-workload-upgrade`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-cluster-api-provider-gcp-apidiff`
* `pull-cluster-api-provider-gcp-build`
* `pull-cluster-api-provider-gcp-e2e-test`
* `pull-cluster-api-provider-gcp-make`
* `pull-cluster-api-provider-gcp-test`
* `pull-cluster-api-provider-gcp-verify`



<details>

In response to [this](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1137#issuecomment-1939154421):

>/test ls


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>
cpanato: /test pull-cluster-api-provider-gcp-conformance
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1137#" title="Author self-approved">bfournie</a>*
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [cpanato](https://github.com/cpanato). For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-gcp).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["cpanato"]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *kubernetes-sigs-cluster-api-gcp* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | f1f917b0d41f3da82528737ce2fe0f670a8018b1 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/kubernetes-sigs-cluster-api-gcp/deploys/65dbc5aabc01210008876c9b |
k8s-ci-robot: @bfournie: 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 | f1f917b0d41f3da82528737ce2fe0f670a8018b1 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-gcp/1137/pull-cluster-api-provider-gcp-verify/1761888089623498752) | 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=1137). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Abfournie). 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: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1137#" title="Author self-approved">bfournie</a>*, *<a href="https://github.com/kubernetes-sigs/cluster-api-provider-gcp/pull/1137#pullrequestreview-1903402220" title="Approved">cpanato</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-gcp).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-gcp/blob/main/OWNERS)~~ [cpanato]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
richardcase: Looks good to me, thanks @bfournie . Good discussion on CEL as part of this :+1: 

/approve
richardcase: /retest
damdo: I asked @bfournie to rebase this on top of `main` once #1157 merges.
I suspect something in the tooling is breaking (could be the Go version as I've seen this break with Netlify already recently).
So a rebase could help here.

Patch:
diff --git a/api/v1beta1/gcpmachine_types.go b/api/v1beta1/gcpmachine_types.go
index 84638555..52faeaae 100644
--- a/api/v1beta1/gcpmachine_types.go
+++ b/api/v1beta1/gcpmachine_types.go
@@ -54,6 +54,9 @@ type AttachedDiskSpec struct {
 	// Defaults to 30GB. For "local-ssd" size is always 375GB.
 	// +optional
 	Size *int64 `json:"size,omitempty"`
+	// EncryptionKey defines the KMS key to be used to encrypt the disk.
+	// +optional
+	EncryptionKey *CustomerEncryptionKey `json:"encryptionKey,omitempty"`
 }
 
 // IPForwarding represents the IP forwarding configuration for the GCP machine.
@@ -146,6 +149,72 @@ const (
 	HostMaintenancePolicyTerminate HostMaintenancePolicy = "Terminate"
 )
 
+// KeyType is a type for disk encryption.
+type KeyType string
+
+const (
+	// CustomerManagedKey (CMEK) references an encryption key stored in Google Cloud KMS.
+	CustomerManagedKey KeyType = "Managed"
+	// CustomerSuppliedKey (CSEK) specifies an encryption key to use.
+	CustomerSuppliedKey KeyType = "Supplied"
+)
+
+// ManagedKey is a reference to a key managed by the Cloud Key Management Service.
+type ManagedKey struct {
+	// KMSKeyName is the name of the encryption key that is stored in Google Cloud KMS. For example:
+	// "kmsKeyName": "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key
+	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:Pattern=`projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+`
+	// +kubebuilder:validation:MaxLength=160
+	KMSKeyName string `json:"kmsKeyName,omitempty"`
+}
+
+// SuppliedKey contains a key for disk encryption. Either RawKey or RSAEncryptedKey must be provided.
+// +kubebuilder:validation:MinProperties=1
+// +kubebuilder:validation:MaxProperties=1
+type SuppliedKey struct {
+	// RawKey specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648
+	// base64 to either encrypt or decrypt this resource. You can provide either the rawKey or the rsaEncryptedKey.
+	// For example: "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
+	// +optional
+	RawKey []byte `json:"rawKey,omitempty"`
+	// RSAEncryptedKey specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption
+	// key to either encrypt or decrypt this resource. You can provide either the rawKey or the
+	// rsaEncryptedKey.
+	// For example: "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
+	// z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
+	// D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
+	// The key must meet the following requirements before you can provide it to Compute Engine:
+	// 1. The key is wrapped using a RSA public key certificate provided by Google.
+	// 2. After being wrapped, the key must be encoded in RFC 4648 base64 encoding.
+	// Gets the RSA public key certificate provided by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem
+	// +optional
+	RSAEncryptedKey []byte `json:"rsaEncryptedKey,omitempty"`
+}
+
+// CustomerEncryptionKey supports both Customer-Managed or Customer-Supplied encryption keys .
+type CustomerEncryptionKey struct {
+	// KeyType is the type of encryption key. Must be either Managed, aka Customer-Managed Encryption Key (CMEK) or
+	// Supplied, aka Customer-Supplied EncryptionKey (CSEK).
+	// +kubebuilder:validation:Enum=Managed;Supplied
+	KeyType KeyType `json:"keyType"`
+	// KMSKeyServiceAccount is the service account being used for the encryption request for the given KMS key.
+	// If absent, the Compute Engine default service account is used. For example:
+	// "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com.
+	// The maximum length is based on the Service Account ID (max 30), Project (max 30), and a valid gcloud email
+	// suffix ("iam.gserviceaccount.com").
+	// +kubebuilder:validation:MaxLength=85
+	// +kubebuilder:validation:Pattern=`[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com`
+	// +optional
+	KMSKeyServiceAccount *string `json:"kmsKeyServiceAccount,omitempty"`
+	// ManagedKey references keys managed by the Cloud Key Management Service. This should be set when KeyType is Managed.
+	// +optional
+	ManagedKey *ManagedKey `json:"managedKey,omitempty"`
+	// SuppliedKey provides the key used to create or manage a disk. This should be set when KeyType is Managed.
+	// +optional
+	SuppliedKey *SuppliedKey `json:"suppliedKey,omitempty"`
+}
+
 // GCPMachineSpec defines the desired state of GCPMachine.
 type GCPMachineSpec struct {
 	// InstanceType is the type of instance to create. Example: n1.standard-2
@@ -252,6 +321,10 @@ type GCPMachineSpec struct {
 	// +kubebuilder:validation:Enum=Enabled;Disabled
 	// +optional
 	ConfidentialCompute *ConfidentialComputePolicy `json:"confidentialCompute,omitempty"`
+
+	// RootDiskEncryptionKey defines the KMS key to be used to encrypt the root disk.
+	// +optional
+	RootDiskEncryptionKey *CustomerEncryptionKey `json:"rootDiskEncryptionKey,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 bc6dc948..2b8057ad 100644
--- a/api/v1beta1/gcpmachine_webhook.go
+++ b/api/v1beta1/gcpmachine_webhook.go
@@ -50,7 +50,11 @@ var _ webhook.Validator = &GCPMachine{}
 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
 func (m *GCPMachine) ValidateCreate() (admission.Warnings, error) {
 	clusterlog.Info("validate create", "name", m.Name)
-	return nil, validateConfidentialCompute(m.Spec)
+
+	if err := validateConfidentialCompute(m.Spec); err != nil {
+		return nil, err
+	}
+	return nil, validateCustomerEncryptionKey(m.Spec)
 }
 
 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
@@ -117,3 +121,39 @@ func validateConfidentialCompute(spec GCPMachineSpec) error {
 	}
 	return nil
 }
+
+func checkKeyType(key *CustomerEncryptionKey) error {
+	switch key.KeyType {
+	case CustomerManagedKey:
+		if key.ManagedKey == nil || key.SuppliedKey != nil {
+			return fmt.Errorf("CustomerEncryptionKey KeyType of Managed requires only ManagedKey to be set")
+		}
+	case CustomerSuppliedKey:
+		if key.SuppliedKey == nil || key.ManagedKey != nil {
+			return fmt.Errorf("CustomerEncryptionKey KeyType of Supplied requires only SuppliedKey to be set")
+		}
+		if len(key.SuppliedKey.RawKey) > 0 && len(key.SuppliedKey.RSAEncryptedKey) > 0 {
+			return fmt.Errorf("CustomerEncryptionKey KeyType of Supplied requires either RawKey or RSAEncryptedKey to be set, not both")
+		}
+	default:
+		return fmt.Errorf("invalid value for CustomerEncryptionKey KeyType %s", key.KeyType)
+	}
+	return nil
+}
+
+func validateCustomerEncryptionKey(spec GCPMachineSpec) error {
+	if spec.RootDiskEncryptionKey != nil {
+		if err := checkKeyType(spec.RootDiskEncryptionKey); err != nil {
+			return err
+		}
+	}
+
+	for _, disk := range spec.AdditionalDisks {
+		if disk.EncryptionKey != nil {
+			if err := checkKeyType(disk.EncryptionKey); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 5ba5b306..d5a60f83 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -40,6 +40,11 @@ func (in *AttachedDiskSpec) DeepCopyInto(out *AttachedDiskSpec) {
 		*out = new(int64)
 		**out = **in
 	}
+	if in.EncryptionKey != nil {
+		in, out := &in.EncryptionKey, &out.EncryptionKey
+		*out = new(CustomerEncryptionKey)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AttachedDiskSpec.
@@ -79,6 +84,36 @@ func (in *BuildParams) DeepCopy() *BuildParams {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CustomerEncryptionKey) DeepCopyInto(out *CustomerEncryptionKey) {
+	*out = *in
+	if in.KMSKeyServiceAccount != nil {
+		in, out := &in.KMSKeyServiceAccount, &out.KMSKeyServiceAccount
+		*out = new(string)
+		**out = **in
+	}
+	if in.ManagedKey != nil {
+		in, out := &in.ManagedKey, &out.ManagedKey
+		*out = new(ManagedKey)
+		**out = **in
+	}
+	if in.SuppliedKey != nil {
+		in, out := &in.SuppliedKey, &out.SuppliedKey
+		*out = new(SuppliedKey)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomerEncryptionKey.
+func (in *CustomerEncryptionKey) DeepCopy() *CustomerEncryptionKey {
+	if in == nil {
+		return nil
+	}
+	out := new(CustomerEncryptionKey)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Filter) DeepCopyInto(out *Filter) {
 	*out = *in
@@ -459,6 +494,11 @@ func (in *GCPMachineSpec) DeepCopyInto(out *GCPMachineSpec) {
 		*out = new(ConfidentialComputePolicy)
 		**out = **in
 	}
+	if in.RootDiskEncryptionKey != nil {
+		in, out := &in.RootDiskEncryptionKey, &out.RootDiskEncryptionKey
+		*out = new(CustomerEncryptionKey)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineSpec.
@@ -633,6 +673,21 @@ func (in Labels) DeepCopy() Labels {
 	return *out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ManagedKey) DeepCopyInto(out *ManagedKey) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedKey.
+func (in *ManagedKey) DeepCopy() *ManagedKey {
+	if in == nil {
+		return nil
+	}
+	out := new(ManagedKey)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MetadataItem) DeepCopyInto(out *MetadataItem) {
 	*out = *in
@@ -906,3 +961,28 @@ func (in Subnets) DeepCopy() Subnets {
 	in.DeepCopyInto(out)
 	return *out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SuppliedKey) DeepCopyInto(out *SuppliedKey) {
+	*out = *in
+	if in.RawKey != nil {
+		in, out := &in.RawKey, &out.RawKey
+		*out = make([]byte, len(*in))
+		copy(*out, *in)
+	}
+	if in.RSAEncryptedKey != nil {
+		in, out := &in.RSAEncryptedKey, &out.RSAEncryptedKey
+		*out = make([]byte, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SuppliedKey.
+func (in *SuppliedKey) DeepCopy() *SuppliedKey {
+	if in == nil {
+		return nil
+	}
+	out := new(SuppliedKey)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go
index 3a0f9f09..15b0dc14 100644
--- a/cloud/scope/machine.go
+++ b/cloud/scope/machine.go
@@ -235,7 +235,7 @@ func (m *MachineScope) InstanceImageSpec() *compute.AttachedDisk {
 		diskType = *t
 	}
 
-	return &compute.AttachedDisk{
+	disk := &compute.AttachedDisk{
 		AutoDelete: true,
 		Boot:       true,
 		InitializeParams: &compute.AttachedDiskInitializeParams{
@@ -245,6 +245,27 @@ func (m *MachineScope) InstanceImageSpec() *compute.AttachedDisk {
 			SourceImage:         sourceImage,
 		},
 	}
+
+	if m.GCPMachine.Spec.RootDiskEncryptionKey != nil {
+		if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerManagedKey && m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey != nil {
+			disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
+				KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KMSKeyName,
+			}
+			if m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount != nil {
+				disk.DiskEncryptionKey.KmsKeyServiceAccount = *m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount
+			}
+		} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey && m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey != nil {
+			disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
+				RawKey:          string(m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey),
+				RsaEncryptedKey: string(m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RSAEncryptedKey),
+			}
+			if m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount != nil {
+				disk.DiskEncryptionKey.KmsKeyServiceAccount = *m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount
+			}
+		}
+	}
+
+	return disk
 }
 
 // InstanceAdditionalDiskSpec returns compute instance additional attched-disk spec.
@@ -269,6 +290,25 @@ func (m *MachineScope) InstanceAdditionalDiskSpec() []*compute.AttachedDisk {
 			// https://cloud.google.com/compute/docs/disks/local-ssd#choose_an_interface
 			additionalDisk.Interface = "NVME"
 		}
+		if disk.EncryptionKey != nil {
+			if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerManagedKey && m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey != nil {
+				additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
+					KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KMSKeyName,
+				}
+				if m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount != nil {
+					additionalDisk.DiskEncryptionKey.KmsKeyServiceAccount = *m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount
+				}
+			} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey && m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey != nil {
+				additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
+					RawKey:          string(m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey),
+					RsaEncryptedKey: string(m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RSAEncryptedKey),
+				}
+				if m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount != nil {
+					additionalDisk.DiskEncryptionKey.KmsKeyServiceAccount = *m.GCPMachine.Spec.RootDiskEncryptionKey.KMSKeyServiceAccount
+				}
+			}
+		}
+
 		additionalDisks = append(additionalDisks, additionalDisk)
 	}
 
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 c965b68b..61311c09 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml
@@ -69,6 +69,77 @@ spec:
                         disk 3. "local-ssd" - Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd).
                         Default is "pd-standard".'
                       type: string
+                    encryptionKey:
+                      description: EncryptionKey defines the KMS key to be used to
+                        encrypt the disk.
+                      properties:
+                        keyType:
+                          description: KeyType is the type of encryption key. Must
+                            be either Managed, aka Customer-Managed Encryption Key
+                            (CMEK) or Supplied, aka Customer-Supplied EncryptionKey
+                            (CSEK).
+                          enum:
+                          - Managed
+                          - Supplied
+                          type: string
+                        kmsKeyServiceAccount:
+                          description: 'KMSKeyServiceAccount is the service account
+                            being used for the encryption request for the given KMS
+                            key. If absent, the Compute Engine default service account
+                            is used. For example: "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com.
+                            The maximum length is based on the Service Account ID
+                            (max 30), Project (max 30), and a valid gcloud email suffix
+                            ("iam.gserviceaccount.com").'
+                          maxLength: 85
+                          pattern: '[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com'
+                          type: string
+                        managedKey:
+                          description: ManagedKey references keys managed by the Cloud
+                            Key Management Service. This should be set when KeyType
+                            is Managed.
+                          properties:
+                            kmsKeyName:
+                              description: 'KMSKeyName is the name of the encryption
+                                key that is stored in Google Cloud KMS. For example:
+                                "kmsKeyName": "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key'
+                              maxLength: 160
+                              pattern: projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+
+                              type: string
+                          type: object
+                        suppliedKey:
+                          description: SuppliedKey provides the key used to create
+                            or manage a disk. This should be set when KeyType is Managed.
+                          maxProperties: 1
+                          minProperties: 1
+                          properties:
+                            rawKey:
+                              description: 'RawKey specifies a 256-bit customer-supplied
+                                encryption key, encoded in RFC 4648 base64 to either
+                                encrypt or decrypt this resource. You can provide
+                                either the rawKey or the rsaEncryptedKey. For example:
+                                "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="'
+                              format: byte
+                              type: string
+                            rsaEncryptedKey:
+                              description: 'RSAEncryptedKey specifies an RFC 4648
+                                base64 encoded, RSA-wrapped 2048-bit customer-supplied
+                                encryption key to either encrypt or decrypt this resource.
+                                You can provide either the rawKey or the rsaEncryptedKey.
+                                For example: "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
+                                z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
+                                D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
+                                The key must meet the following requirements before
+                                you can provide it to Compute Engine: 1. The key is
+                                wrapped using a RSA public key certificate provided
+                                by Google. 2. After being wrapped, the key must be
+                                encoded in RFC 4648 base64 encoding. Gets the RSA
+                                public key certificate provided by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem'
+                              format: byte
+                              type: string
+                          type: object
+                      required:
+                      - keyType
+                      type: object
                     size:
                       description: Size is the size of the disk in GBs. Defaults to
                         30GB. For "local-ssd" size is always 375GB.
@@ -226,6 +297,73 @@ spec:
                   types of root volumes: 1. "pd-standard" - Standard (HDD) persistent
                   disk 2. "pd-ssd" - SSD persistent disk Default is "pd-standard".'
                 type: string
+              rootDiskEncryptionKey:
+                description: RootDiskEncryptionKey defines the KMS key to be used
+                  to encrypt the root disk.
+                properties:
+                  keyType:
+                    description: KeyType is the type of encryption key. Must be either
+                      Managed, aka Customer-Managed Encryption Key (CMEK) or Supplied,
+                      aka Customer-Supplied EncryptionKey (CSEK).
+                    enum:
+                    - Managed
+                    - Supplied
+                    type: string
+                  kmsKeyServiceAccount:
+                    description: 'KMSKeyServiceAccount is the service account being
+                      used for the encryption request for the given KMS key. If absent,
+                      the Compute Engine default service account is used. For example:
+                      "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com.
+                      The maximum length is based on the Service Account ID (max 30),
+                      Project (max 30), and a valid gcloud email suffix ("iam.gserviceaccount.com").'
+                    maxLength: 85
+                    pattern: '[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com'
+                    type: string
+                  managedKey:
+                    description: ManagedKey references keys managed by the Cloud Key
+                      Management Service. This should be set when KeyType is Managed.
+                    properties:
+                      kmsKeyName:
+                        description: 'KMSKeyName is the name of the encryption key
+                          that is stored in Google Cloud KMS. For example: "kmsKeyName":
+                          "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key'
+                        maxLength: 160
+                        pattern: projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+
+                        type: string
+                    type: object
+                  suppliedKey:
+                    description: SuppliedKey provides the key used to create or manage
+                      a disk. This should be set when KeyType is Managed.
+                    maxProperties: 1
+                    minProperties: 1
+                    properties:
+                      rawKey:
+                        description: 'RawKey specifies a 256-bit customer-supplied
+                          encryption key, encoded in RFC 4648 base64 to either encrypt
+                          or decrypt this resource. You can provide either the rawKey
+                          or the rsaEncryptedKey. For example: "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="'
+                        format: byte
+                        type: string
+                      rsaEncryptedKey:
+                        description: 'RSAEncryptedKey specifies an RFC 4648 base64
+                          encoded, RSA-wrapped 2048-bit customer-supplied encryption
+                          key to either encrypt or decrypt this resource. You can
+                          provide either the rawKey or the rsaEncryptedKey. For example:
+                          "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
+                          z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
+                          D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
+                          The key must meet the following requirements before you
+                          can provide it to Compute Engine: 1. The key is wrapped
+                          using a RSA public key certificate provided by Google. 2.
+                          After being wrapped, the key must be encoded in RFC 4648
+                          base64 encoding. Gets the RSA public key certificate provided
+                          by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem'
+                        format: byte
+                        type: string
+                    type: object
+                required:
+                - keyType
+                type: object
               serviceAccounts:
                 description: 'ServiceAccount specifies the service account email and
                   which scopes to assign to the machine. Defaults to: email: "default",
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 ceacc3c0..ead89e73 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml
@@ -80,6 +80,81 @@ spec:
                                 Local SSD disk (https://cloud.google.com/compute/docs/disks/local-ssd).
                                 Default is "pd-standard".'
                               type: string
+                            encryptionKey:
+                              description: EncryptionKey defines the KMS key to be
+                                used to encrypt the disk.
+                              properties:
+                                keyType:
+                                  description: KeyType is the type of encryption key.
+                                    Must be either Managed, aka Customer-Managed Encryption
+                                    Key (CMEK) or Supplied, aka Customer-Supplied
+                                    EncryptionKey (CSEK).
+                                  enum:
+                                  - Managed
+                                  - Supplied
+                                  type: string
+                                kmsKeyServiceAccount:
+                                  description: 'KMSKeyServiceAccount is the service
+                                    account being used for the encryption request
+                                    for the given KMS key. If absent, the Compute
+                                    Engine default service account is used. For example:
+                                    "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com.
+                                    The maximum length is based on the Service Account
+                                    ID (max 30), Project (max 30), and a valid gcloud
+                                    email suffix ("iam.gserviceaccount.com").'
+                                  maxLength: 85
+                                  pattern: '[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com'
+                                  type: string
+                                managedKey:
+                                  description: ManagedKey references keys managed
+                                    by the Cloud Key Management Service. This should
+                                    be set when KeyType is Managed.
+                                  properties:
+                                    kmsKeyName:
+                                      description: 'KMSKeyName is the name of the
+                                        encryption key that is stored in Google Cloud
+                                        KMS. For example: "kmsKeyName": "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key'
+                                      maxLength: 160
+                                      pattern: projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+
+                                      type: string
+                                  type: object
+                                suppliedKey:
+                                  description: SuppliedKey provides the key used to
+                                    create or manage a disk. This should be set when
+                                    KeyType is Managed.
+                                  maxProperties: 1
+                                  minProperties: 1
+                                  properties:
+                                    rawKey:
+                                      description: 'RawKey specifies a 256-bit customer-supplied
+                                        encryption key, encoded in RFC 4648 base64
+                                        to either encrypt or decrypt this resource.
+                                        You can provide either the rawKey or the rsaEncryptedKey.
+                                        For example: "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="'
+                                      format: byte
+                                      type: string
+                                    rsaEncryptedKey:
+                                      description: 'RSAEncryptedKey specifies an RFC
+                                        4648 base64 encoded, RSA-wrapped 2048-bit
+                                        customer-supplied encryption key to either
+                                        encrypt or decrypt this resource. You can
+                                        provide either the rawKey or the rsaEncryptedKey.
+                                        For example: "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
+                                        z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
+                                        D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
+                                        The key must meet the following requirements
+                                        before you can provide it to Compute Engine:
+                                        1. The key is wrapped using a RSA public key
+                                        certificate provided by Google. 2. After being
+                                        wrapped, the key must be encoded in RFC 4648
+                                        base64 encoding. Gets the RSA public key certificate
+                                        provided by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem'
+                                      format: byte
+                                      type: string
+                                  type: object
+                              required:
+                              - keyType
+                              type: object
                             size:
                               description: Size is the size of the disk in GBs. Defaults
                                 to 30GB. For "local-ssd" size is always 375GB.
@@ -242,6 +317,80 @@ spec:
                           (HDD) persistent disk 2. "pd-ssd" - SSD persistent disk
                           Default is "pd-standard".'
                         type: string
+                      rootDiskEncryptionKey:
+                        description: RootDiskEncryptionKey defines the KMS key to
+                          be used to encrypt the root disk.
+                        properties:
+                          keyType:
+                            description: KeyType is the type of encryption key. Must
+                              be either Managed, aka Customer-Managed Encryption Key
+                              (CMEK) or Supplied, aka Customer-Supplied EncryptionKey
+                              (CSEK).
+                            enum:
+                            - Managed
+                            - Supplied
+                            type: string
+                          kmsKeyServiceAccount:
+                            description: 'KMSKeyServiceAccount is the service account
+                              being used for the encryption request for the given
+                              KMS key. If absent, the Compute Engine default service
+                              account is used. For example: "kmsKeyServiceAccount":
+                              "name@project_id.iam.gserviceaccount.com. The maximum
+                              length is based on the Service Account ID (max 30),
+                              Project (max 30), and a valid gcloud email suffix ("iam.gserviceaccount.com").'
+                            maxLength: 85
+                            pattern: '[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com'
+                            type: string
+                          managedKey:
+                            description: ManagedKey references keys managed by the
+                              Cloud Key Management Service. This should be set when
+                              KeyType is Managed.
+                            properties:
+                              kmsKeyName:
+                                description: 'KMSKeyName is the name of the encryption
+                                  key that is stored in Google Cloud KMS. For example:
+                                  "kmsKeyName": "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key'
+                                maxLength: 160
+                                pattern: projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+
+                                type: string
+                            type: object
+                          suppliedKey:
+                            description: SuppliedKey provides the key used to create
+                              or manage a disk. This should be set when KeyType is
+                              Managed.
+                            maxProperties: 1
+                            minProperties: 1
+                            properties:
+                              rawKey:
+                                description: 'RawKey specifies a 256-bit customer-supplied
+                                  encryption key, encoded in RFC 4648 base64 to either
+                                  encrypt or decrypt this resource. You can provide
+                                  either the rawKey or the rsaEncryptedKey. For example:
+                                  "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="'
+                                format: byte
+                                type: string
+                              rsaEncryptedKey:
+                                description: 'RSAEncryptedKey specifies an RFC 4648
+                                  base64 encoded, RSA-wrapped 2048-bit customer-supplied
+                                  encryption key to either encrypt or decrypt this
+                                  resource. You can provide either the rawKey or the
+                                  rsaEncryptedKey. For example: "rsaEncryptedKey":
+                                  "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
+                                  z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
+                                  D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
+                                  The key must meet the following requirements before
+                                  you can provide it to Compute Engine: 1. The key
+                                  is wrapped using a RSA public key certificate provided
+                                  by Google. 2. After being wrapped, the key must
+                                  be encoded in RFC 4648 base64 encoding. Gets the
+                                  RSA public key certificate provided by Google at:
+                                  https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem'
+                                format: byte
+                                type: string
+                            type: object
+                        required:
+                        - keyType
+                        type: object
                       serviceAccounts:
                         description: 'ServiceAccount specifies the service account
                           email and which scopes to assign to the machine. Defaults


Test patch:
diff --git a/api/v1beta1/gcpmachine_webhook_test.go b/api/v1beta1/gcpmachine_webhook_test.go
index 2eab4f9b..a1b38a38 100644
--- a/api/v1beta1/gcpmachine_webhook_test.go
+++ b/api/v1beta1/gcpmachine_webhook_test.go
@@ -85,6 +85,86 @@ func TestGCPMachine_ValidateCreate(t *testing.T) {
 			},
 			wantErr: true,
 		},
+		{
+			name: "GCPMachine with RootDiskEncryptionKey KeyType Managed and Managed field set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					RootDiskEncryptionKey: &CustomerEncryptionKey{
+						KeyType: CustomerManagedKey,
+						ManagedKey: &ManagedKey{
+							KMSKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachine with RootDiskEncryptionKey KeyType Managed and Managed field not set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					RootDiskEncryptionKey: &CustomerEncryptionKey{
+						KeyType: CustomerManagedKey,
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and Supplied field not set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					RootDiskEncryptionKey: &CustomerEncryptionKey{
+						KeyType: CustomerSuppliedKey,
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachine with AdditionalDisk Encryption KeyType Managed and Managed field not set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					AdditionalDisks: []AttachedDiskSpec{
+						{
+							EncryptionKey: &CustomerEncryptionKey{
+								KeyType: CustomerManagedKey,
+							},
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and one Supplied field set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					RootDiskEncryptionKey: &CustomerEncryptionKey{
+						KeyType: CustomerSuppliedKey,
+						SuppliedKey: &SuppliedKey{
+							RawKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and both Supplied fields set",
+			GCPMachine: &GCPMachine{
+				Spec: GCPMachineSpec{
+					RootDiskEncryptionKey: &CustomerEncryptionKey{
+						KeyType: CustomerSuppliedKey,
+						SuppliedKey: &SuppliedKey{
+							RawKey:          []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
+							RSAEncryptedKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
+						},
+					},
+				},
+			},
+			wantErr: true,
+		},
 	}
 	for _, test := range tests {
 		test := test
diff --git a/cloud/services/compute/instances/reconcile_test.go b/cloud/services/compute/instances/reconcile_test.go
index 54019c3f..dd6b6757 100644
--- a/cloud/services/compute/instances/reconcile_test.go
+++ b/cloud/services/compute/instances/reconcile_test.go
@@ -460,12 +460,12 @@ func TestService_createOrGetInstance(t *testing.T) {
 					ResourceManagerTags: map[string]string{},
 				},
 				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,
 				},
+				Scheduling: &compute.Scheduling{
+					OnHostMaintenance: strings.ToUpper(string(infrav1.HostMaintenancePolicyTerminate)),
+				},
 				ServiceAccounts: []*compute.ServiceAccount{
 					{
 						Email:  "default",
@@ -609,6 +609,225 @@ func TestService_createOrGetInstance(t *testing.T) {
 				Zone: "us-central1-a",
 			},
 		},
+		{
+			name: "instance does not exist (should create instance) with Customer-Managed boot DiskEncryption",
+			scope: func() Scope {
+				machineScope.GCPMachine = getFakeGCPMachine()
+				diskEncryption := infrav1.CustomerEncryptionKey{
+					KeyType: infrav1.CustomerManagedKey,
+					ManagedKey: &infrav1.ManagedKey{
+						KMSKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
+					},
+				}
+				machineScope.GCPMachine.Spec.RootDiskEncryptionKey = &diskEncryption
+				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",
+							ResourceManagerTags: map[string]string{},
+						},
+						DiskEncryptionKey: &compute.CustomerEncryptionKey{
+							KmsKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
+						},
+					},
+				},
+				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: ptr.To[string]("Zm9vCg=="),
+						},
+					},
+				},
+				NetworkInterfaces: []*compute.NetworkInterface{
+					{
+						Network: "projects/my-proj/global/networks/default",
+					},
+				},
+				Params: &compute.InstanceParams{
+					ResourceManagerTags: map[string]string{},
+				},
+				SelfLink:   "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
+				Scheduling: &compute.Scheduling{},
+				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 Customer-Supplied Raw DiskEncryption",
+			scope: func() Scope {
+				machineScope.GCPMachine = getFakeGCPMachine()
+				diskEncryption := infrav1.CustomerEncryptionKey{
+					KeyType: infrav1.CustomerSuppliedKey,
+					SuppliedKey: &infrav1.SuppliedKey{
+						RawKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
+					},
+				}
+				machineScope.GCPMachine.Spec.RootDiskEncryptionKey = &diskEncryption
+				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",
+							ResourceManagerTags: map[string]string{},
+						},
+						DiskEncryptionKey: &compute.CustomerEncryptionKey{
+							RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=",
+						},
+					},
+				},
+				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: ptr.To[string]("Zm9vCg=="),
+						},
+					},
+				},
+				NetworkInterfaces: []*compute.NetworkInterface{
+					{
+						Network: "projects/my-proj/global/networks/default",
+					},
+				},
+				Params: &compute.InstanceParams{
+					ResourceManagerTags: map[string]string{},
+				},
+				SelfLink:   "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
+				Scheduling: &compute.Scheduling{},
+				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 Customer-Supplied RSA DiskEncryption",
+			scope: func() Scope {
+				machineScope.GCPMachine = getFakeGCPMachine()
+				diskEncryption := infrav1.CustomerEncryptionKey{
+					KeyType: infrav1.CustomerSuppliedKey,
+					SuppliedKey: &infrav1.SuppliedKey{
+						RSAEncryptedKey: []byte("ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHiz0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDiD6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="),
+					},
+				}
+				machineScope.GCPMachine.Spec.RootDiskEncryptionKey = &diskEncryption
+				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",
+							ResourceManagerTags: map[string]string{},
+						},
+						DiskEncryptionKey: &compute.CustomerEncryptionKey{
+							RsaEncryptedKey: "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHiz0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDiD6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe==",
+						},
+					},
+				},
+				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: ptr.To[string]("Zm9vCg=="),
+						},
+					},
+				},
+				NetworkInterfaces: []*compute.NetworkInterface{
+					{
+						Network: "projects/my-proj/global/networks/default",
+					},
+				},
+				Params: &compute.InstanceParams{
+					ResourceManagerTags: map[string]string{},
+				},
+				SelfLink:   "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
+				Scheduling: &compute.Scheduling{},
+				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",
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {