Skip to content

Commit

Permalink
Add support for disk encryption key in GCPMachine
Browse files Browse the repository at this point in the history
Add support for the disk encryption key for the boot disk and
and additional disks.
  • Loading branch information
bfournie committed Feb 12, 2024
1 parent 9788374 commit 82942c4
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 4 deletions.
58 changes: 58 additions & 0 deletions api/v1beta1/gcpmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -146,6 +149,57 @@ 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 {
// 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
KmsKeyName string `json:"kmsKeyName,omitempty"`
}

// SuppliedKey contains a key for disk encryption.
type SuppliedKey struct {
// 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="
RawKey string `json:"rawKey,omitempty"`
// 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
RsaEncryptedKey string `json:"rsaEncryptedKey,omitempty"`
}

// CustomerEncryptionKey supports both Customer-Managed or Customer-Supplied encryption keys .
type CustomerEncryptionKey struct {
// The type of encryption key. Must be either Managed, aka Customer-Managed Encryption Key (CMEK) or
// Supplied, aka Customer-Supplied EncryptionKey (CSEK).
KeyType KeyType `json:"keyType"`
// 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/
KmsKeyServiceAccount string `json:"kmsKeyServiceAccount,omitempty"`
// The Customer-Managed Encryption Key references keys managed by the Cloud Key Management Service.
ManagedKey *ManagedKey `json:"managedKey,omitempty"`
// The Customer-Supplied Encryption Key provides the key used to create or manage a disk.
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
Expand Down Expand Up @@ -252,6 +306,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.
Expand Down
65 changes: 65 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 33 additions & 1 deletion cloud/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -245,6 +245,23 @@ func (m *MachineScope) InstanceImageSpec() *compute.AttachedDisk {
SourceImage: sourceImage,
},
}

if m.GCPMachine.Spec.RootDiskEncryptionKey != nil {
if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerManagedKey {
disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KmsKeyName,
}
} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey {
disk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
RawKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey,
RsaEncryptedKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RsaEncryptedKey,
}
}
}

return disk
}

// InstanceAdditionalDiskSpec returns compute instance additional attched-disk spec.
Expand All @@ -269,6 +286,21 @@ 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 {
additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
KmsKeyName: m.GCPMachine.Spec.RootDiskEncryptionKey.ManagedKey.KmsKeyName,
}
} else if m.GCPMachine.Spec.RootDiskEncryptionKey.KeyType == infrav1.CustomerSuppliedKey {
additionalDisk.DiskEncryptionKey = &compute.CustomerEncryptionKey{
KmsKeyServiceAccount: m.GCPMachine.Spec.RootDiskEncryptionKey.KmsKeyServiceAccount,
RawKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RawKey,
RsaEncryptedKey: m.GCPMachine.Spec.RootDiskEncryptionKey.SuppliedKey.RsaEncryptedKey,
}
}
}

additionalDisks = append(additionalDisks, additionalDisk)
}

Expand Down
152 changes: 149 additions & 3 deletions cloud/services/compute/instances/reconcile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -609,6 +609,152 @@ 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 boot DiskEncryption",
scope: func() Scope {
machineScope.GCPMachine = getFakeGCPMachine()
diskEncryption := infrav1.CustomerEncryptionKey{
KeyType: infrav1.CustomerSuppliedKey,
SuppliedKey: &infrav1.SuppliedKey{
RawKey: "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",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading

0 comments on commit 82942c4

Please sign in to comment.