Skip to content

Commit 82942c4

Browse files
committed
Add support for disk encryption key in GCPMachine
Add support for the disk encryption key for the boot disk and and additional disks.
1 parent 9788374 commit 82942c4

File tree

6 files changed

+528
-4
lines changed

6 files changed

+528
-4
lines changed

api/v1beta1/gcpmachine_types.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ type AttachedDiskSpec struct {
5454
// Defaults to 30GB. For "local-ssd" size is always 375GB.
5555
// +optional
5656
Size *int64 `json:"size,omitempty"`
57+
// EncryptionKey defines the KMS key to be used to encrypt the disk.
58+
// +optional
59+
EncryptionKey *CustomerEncryptionKey `json:"encryptionKey,omitempty"`
5760
}
5861

5962
// IPForwarding represents the IP forwarding configuration for the GCP machine.
@@ -146,6 +149,57 @@ const (
146149
HostMaintenancePolicyTerminate HostMaintenancePolicy = "Terminate"
147150
)
148151

152+
// KeyType is a type for disk encryption.
153+
type KeyType string
154+
155+
const (
156+
// CustomerManagedKey (CMEK) references an encryption key stored in Google Cloud KMS.
157+
CustomerManagedKey KeyType = "Managed"
158+
// CustomerSuppliedKey (CSEK) specifies an encryption key to use.
159+
CustomerSuppliedKey KeyType = "Supplied"
160+
)
161+
162+
// ManagedKey is a reference to a key managed by the Cloud Key Management Service.
163+
type ManagedKey struct {
164+
// The name of the encryption key that is stored in Google Cloud KMS. For example:
165+
// "kmsKeyName": "projects/kms_project_id/locations/region/keyRings/key_region/cryptoKeys/key
166+
KmsKeyName string `json:"kmsKeyName,omitempty"`
167+
}
168+
169+
// SuppliedKey contains a key for disk encryption.
170+
type SuppliedKey struct {
171+
// Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648
172+
// base64 to either encrypt or decrypt this resource. You can provide either the rawKey or the rsaEncryptedKey.
173+
// For example: "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
174+
RawKey string `json:"rawKey,omitempty"`
175+
// Specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption
176+
// key to either encrypt or decrypt this resource. You can provide either the rawKey or the
177+
// rsaEncryptedKey.
178+
// For example: "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
179+
// z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
180+
// D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
181+
// The key must meet the following requirements before you can provide it to Compute Engine:
182+
// 1. The key is wrapped using a RSA public key certificate provided by Google.
183+
// 2. After being wrapped, the key must be encoded in RFC 4648 base64 encoding.
184+
// Gets the RSA public key certificate provided by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem
185+
RsaEncryptedKey string `json:"rsaEncryptedKey,omitempty"`
186+
}
187+
188+
// CustomerEncryptionKey supports both Customer-Managed or Customer-Supplied encryption keys .
189+
type CustomerEncryptionKey struct {
190+
// The type of encryption key. Must be either Managed, aka Customer-Managed Encryption Key (CMEK) or
191+
// Supplied, aka Customer-Supplied EncryptionKey (CSEK).
192+
KeyType KeyType `json:"keyType"`
193+
// The service account being used for the encryption request for the given KMS key.
194+
// If absent, the Compute Engine default service account is used. For example:
195+
// "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com/
196+
KmsKeyServiceAccount string `json:"kmsKeyServiceAccount,omitempty"`
197+
// The Customer-Managed Encryption Key references keys managed by the Cloud Key Management Service.
198+
ManagedKey *ManagedKey `json:"managedKey,omitempty"`
199+
// The Customer-Supplied Encryption Key provides the key used to create or manage a disk.
200+
SuppliedKey *SuppliedKey `json:"suppliedKey,omitempty"`
201+
}
202+
149203
// GCPMachineSpec defines the desired state of GCPMachine.
150204
type GCPMachineSpec struct {
151205
// InstanceType is the type of instance to create. Example: n1.standard-2
@@ -252,6 +306,10 @@ type GCPMachineSpec struct {
252306
// +kubebuilder:validation:Enum=Enabled;Disabled
253307
// +optional
254308
ConfidentialCompute *ConfidentialComputePolicy `json:"confidentialCompute,omitempty"`
309+
310+
// RootDiskEncryptionKey defines the KMS key to be used to encrypt the root disk.
311+
// +optional
312+
RootDiskEncryptionKey *CustomerEncryptionKey `json:"rootDiskEncryptionKey,omitempty"`
255313
}
256314

257315
// MetadataItem defines a single piece of metadata associated with an instance.

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/scope/machine.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ func (m *MachineScope) InstanceImageSpec() *compute.AttachedDisk {
235235
diskType = *t
236236
}
237237

238-
return &compute.AttachedDisk{
238+
disk := &compute.AttachedDisk{
239239
AutoDelete: true,
240240
Boot: true,
241241
InitializeParams: &compute.AttachedDiskInitializeParams{
@@ -245,6 +245,23 @@ func (m *MachineScope) InstanceImageSpec() *compute.AttachedDisk {
245245
SourceImage: sourceImage,
246246
},
247247
}
248+
249+
if m.GCPMachine.Spec.RootDiskEncryptionKey != nil {
250+
if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerManagedKey {
251+
disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
252+
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
253+
KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KmsKeyName,
254+
}
255+
} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey {
256+
disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
257+
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
258+
RawKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey,
259+
RsaEncryptedKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RsaEncryptedKey,
260+
}
261+
}
262+
}
263+
264+
return disk
248265
}
249266

250267
// InstanceAdditionalDiskSpec returns compute instance additional attched-disk spec.
@@ -269,6 +286,21 @@ func (m *MachineScope) InstanceAdditionalDiskSpec() []*compute.AttachedDisk {
269286
// https://cloud.google.com/compute/docs/disks/local-ssd#choose_an_interface
270287
additionalDisk.Interface = "NVME"
271288
}
289+
if disk.EncryptionKey != nil {
290+
if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerManagedKey {
291+
additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
292+
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
293+
KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KmsKeyName,
294+
}
295+
} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey {
296+
additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
297+
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
298+
RawKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey,
299+
RsaEncryptedKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RsaEncryptedKey,
300+
}
301+
}
302+
}
303+
272304
additionalDisks = append(additionalDisks, additionalDisk)
273305
}
274306

cloud/services/compute/instances/reconcile_test.go

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,12 @@ func TestService_createOrGetInstance(t *testing.T) {
460460
ResourceManagerTags: map[string]string{},
461461
},
462462
SelfLink: "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
463-
Scheduling: &compute.Scheduling{
464-
OnHostMaintenance: strings.ToUpper(string(infrav1.HostMaintenancePolicyTerminate)),
465-
},
466463
ConfidentialInstanceConfig: &compute.ConfidentialInstanceConfig{
467464
EnableConfidentialCompute: true,
468465
},
466+
Scheduling: &compute.Scheduling{
467+
OnHostMaintenance: strings.ToUpper(string(infrav1.HostMaintenancePolicyTerminate)),
468+
},
469469
ServiceAccounts: []*compute.ServiceAccount{
470470
{
471471
Email: "default",
@@ -609,6 +609,152 @@ func TestService_createOrGetInstance(t *testing.T) {
609609
Zone: "us-central1-a",
610610
},
611611
},
612+
{
613+
name: "instance does not exist (should create instance) with Customer-Managed boot DiskEncryption",
614+
scope: func() Scope {
615+
machineScope.GCPMachine = getFakeGCPMachine()
616+
diskEncryption := infrav1.CustomerEncryptionKey{
617+
KeyType: infrav1.CustomerManagedKey,
618+
ManagedKey: &infrav1.ManagedKey{
619+
KmsKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
620+
},
621+
}
622+
machineScope.GCPMachine.Spec.RootDiskEncryptionKey = &diskEncryption
623+
return machineScope
624+
},
625+
mockInstance: &cloud.MockInstances{
626+
ProjectRouter: &cloud.SingleProjectRouter{ID: "proj-id"},
627+
Objects: map[meta.Key]*cloud.MockInstancesObj{},
628+
},
629+
want: &compute.Instance{
630+
Name: "my-machine",
631+
CanIpForward: true,
632+
Disks: []*compute.AttachedDisk{
633+
{
634+
AutoDelete: true,
635+
Boot: true,
636+
InitializeParams: &compute.AttachedDiskInitializeParams{
637+
DiskType: "zones/us-central1-c/diskTypes/pd-standard",
638+
SourceImage: "projects/my-proj/global/images/family/capi-ubuntu-1804-k8s-v1-19",
639+
ResourceManagerTags: map[string]string{},
640+
},
641+
DiskEncryptionKey: &compute.CustomerEncryptionKey{
642+
KmsKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
643+
},
644+
},
645+
},
646+
Labels: map[string]string{
647+
"capg-role": "node",
648+
"capg-cluster-my-cluster": "owned",
649+
"foo": "bar",
650+
},
651+
MachineType: "zones/us-central1-c/machineTypes",
652+
Metadata: &compute.Metadata{
653+
Items: []*compute.MetadataItems{
654+
{
655+
Key: "user-data",
656+
Value: ptr.To[string]("Zm9vCg=="),
657+
},
658+
},
659+
},
660+
NetworkInterfaces: []*compute.NetworkInterface{
661+
{
662+
Network: "projects/my-proj/global/networks/default",
663+
},
664+
},
665+
Params: &compute.InstanceParams{
666+
ResourceManagerTags: map[string]string{},
667+
},
668+
SelfLink: "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
669+
Scheduling: &compute.Scheduling{},
670+
ServiceAccounts: []*compute.ServiceAccount{
671+
{
672+
Email: "default",
673+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
674+
},
675+
},
676+
Tags: &compute.Tags{
677+
Items: []string{
678+
"my-cluster-node",
679+
"my-cluster",
680+
},
681+
},
682+
Zone: "us-central1-c",
683+
},
684+
},
685+
{
686+
name: "instance does not exist (should create instance) with Customer-Supplied boot DiskEncryption",
687+
scope: func() Scope {
688+
machineScope.GCPMachine = getFakeGCPMachine()
689+
diskEncryption := infrav1.CustomerEncryptionKey{
690+
KeyType: infrav1.CustomerSuppliedKey,
691+
SuppliedKey: &infrav1.SuppliedKey{
692+
RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=",
693+
},
694+
}
695+
machineScope.GCPMachine.Spec.RootDiskEncryptionKey = &diskEncryption
696+
return machineScope
697+
},
698+
mockInstance: &cloud.MockInstances{
699+
ProjectRouter: &cloud.SingleProjectRouter{ID: "proj-id"},
700+
Objects: map[meta.Key]*cloud.MockInstancesObj{},
701+
},
702+
want: &compute.Instance{
703+
Name: "my-machine",
704+
CanIpForward: true,
705+
Disks: []*compute.AttachedDisk{
706+
{
707+
AutoDelete: true,
708+
Boot: true,
709+
InitializeParams: &compute.AttachedDiskInitializeParams{
710+
DiskType: "zones/us-central1-c/diskTypes/pd-standard",
711+
SourceImage: "projects/my-proj/global/images/family/capi-ubuntu-1804-k8s-v1-19",
712+
ResourceManagerTags: map[string]string{},
713+
},
714+
DiskEncryptionKey: &compute.CustomerEncryptionKey{
715+
RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=",
716+
},
717+
},
718+
},
719+
Labels: map[string]string{
720+
"capg-role": "node",
721+
"capg-cluster-my-cluster": "owned",
722+
"foo": "bar",
723+
},
724+
MachineType: "zones/us-central1-c/machineTypes",
725+
Metadata: &compute.Metadata{
726+
Items: []*compute.MetadataItems{
727+
{
728+
Key: "user-data",
729+
Value: ptr.To[string]("Zm9vCg=="),
730+
},
731+
},
732+
},
733+
NetworkInterfaces: []*compute.NetworkInterface{
734+
{
735+
Network: "projects/my-proj/global/networks/default",
736+
},
737+
},
738+
Params: &compute.InstanceParams{
739+
ResourceManagerTags: map[string]string{},
740+
},
741+
SelfLink: "https://www.googleapis.com/compute/v1/projects/proj-id/zones/us-central1-c/instances/my-machine",
742+
Scheduling: &compute.Scheduling{},
743+
ServiceAccounts: []*compute.ServiceAccount{
744+
{
745+
Email: "default",
746+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
747+
},
748+
},
749+
Tags: &compute.Tags{
750+
Items: []string{
751+
"my-cluster-node",
752+
"my-cluster",
753+
},
754+
},
755+
Zone: "us-central1-c",
756+
},
757+
},
612758
}
613759
for _, tt := range tests {
614760
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)