Skip to content

Commit 0c780af

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 ce36a0e commit 0c780af

File tree

8 files changed

+824
-5
lines changed

8 files changed

+824
-5
lines changed

api/v1beta1/gcpmachine_types.go

Lines changed: 73 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,72 @@ 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+
// KMSKeyName is 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+
// +kubebuilder:validation:Required
167+
// +kubebuilder:validation:Pattern=`projects\/[-_[A-Za-z0-9]+\/locations\/[-_[A-Za-z0-9]+\/keyRings\/[-_[A-Za-z0-9]+\/cryptoKeys\/[-_[A-Za-z0-9]+`
168+
// +kubebuilder:validation:MaxLength=160
169+
KMSKeyName string `json:"kmsKeyName,omitempty"`
170+
}
171+
172+
// SuppliedKey contains a key for disk encryption. Either RawKey or RSAEncryptedKey must be provided.
173+
// +kubebuilder:validation:MinProperties=1
174+
// +kubebuilder:validation:MaxProperties=1
175+
type SuppliedKey struct {
176+
// RawKey specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648
177+
// base64 to either encrypt or decrypt this resource. You can provide either the rawKey or the rsaEncryptedKey.
178+
// For example: "rawKey": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
179+
// +optional
180+
RawKey []byte `json:"rawKey,omitempty"`
181+
// RSAEncryptedKey specifies an RFC 4648 base64 encoded, RSA-wrapped 2048-bit customer-supplied encryption
182+
// key to either encrypt or decrypt this resource. You can provide either the rawKey or the
183+
// rsaEncryptedKey.
184+
// For example: "rsaEncryptedKey": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHi
185+
// z0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDi
186+
// D6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oe=="
187+
// The key must meet the following requirements before you can provide it to Compute Engine:
188+
// 1. The key is wrapped using a RSA public key certificate provided by Google.
189+
// 2. After being wrapped, the key must be encoded in RFC 4648 base64 encoding.
190+
// Gets the RSA public key certificate provided by Google at: https://cloud-certs.storage.googleapis.com/google-cloud-csek-ingress.pem
191+
// +optional
192+
RSAEncryptedKey []byte `json:"rsaEncryptedKey,omitempty"`
193+
}
194+
195+
// CustomerEncryptionKey supports both Customer-Managed or Customer-Supplied encryption keys .
196+
type CustomerEncryptionKey struct {
197+
// KeyType is the type of encryption key. Must be either Managed, aka Customer-Managed Encryption Key (CMEK) or
198+
// Supplied, aka Customer-Supplied EncryptionKey (CSEK).
199+
// +kubebuilder:validation:Enum=Managed;Supplied
200+
KeyType KeyType `json:"keyType"`
201+
// KMSKeyServiceAccount is the service account being used for the encryption request for the given KMS key.
202+
// If absent, the Compute Engine default service account is used. For example:
203+
// "kmsKeyServiceAccount": "name@project_id.iam.gserviceaccount.com.
204+
// The maximum length is based on the Service Account ID (max 30), Project (max 30), and a valid gcloud email
205+
// suffix ("iam.gserviceaccount.com").
206+
// +kubebuilder:validation:MaxLength=85
207+
// +kubebuilder:validation:Pattern=`[-_[A-Za-z0-9]+@[-_[A-Za-z0-9]+.iam.gserviceaccount.com`
208+
// +optional
209+
KMSKeyServiceAccount *string `json:"kmsKeyServiceAccount,omitempty"`
210+
// ManagedKey references keys managed by the Cloud Key Management Service. This should be set when KeyType is Managed.
211+
// +optional
212+
ManagedKey *ManagedKey `json:"managedKey,omitempty"`
213+
// SuppliedKey provides the key used to create or manage a disk. This should be set when KeyType is Managed.
214+
// +optional
215+
SuppliedKey *SuppliedKey `json:"suppliedKey,omitempty"`
216+
}
217+
149218
// GCPMachineSpec defines the desired state of GCPMachine.
150219
type GCPMachineSpec struct {
151220
// InstanceType is the type of instance to create. Example: n1.standard-2
@@ -252,6 +321,10 @@ type GCPMachineSpec struct {
252321
// +kubebuilder:validation:Enum=Enabled;Disabled
253322
// +optional
254323
ConfidentialCompute *ConfidentialComputePolicy `json:"confidentialCompute,omitempty"`
324+
325+
// RootDiskEncryptionKey defines the KMS key to be used to encrypt the root disk.
326+
// +optional
327+
RootDiskEncryptionKey *CustomerEncryptionKey `json:"rootDiskEncryptionKey,omitempty"`
255328
}
256329

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

api/v1beta1/gcpmachine_webhook.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ var _ webhook.Validator = &GCPMachine{}
5050
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
5151
func (m *GCPMachine) ValidateCreate() (admission.Warnings, error) {
5252
clusterlog.Info("validate create", "name", m.Name)
53-
return nil, validateConfidentialCompute(m.Spec)
53+
54+
if err := validateConfidentialCompute(m.Spec); err != nil {
55+
return nil, err
56+
}
57+
return nil, validateCustomerEncryptionKey(m.Spec)
5458
}
5559

5660
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
@@ -117,3 +121,39 @@ func validateConfidentialCompute(spec GCPMachineSpec) error {
117121
}
118122
return nil
119123
}
124+
125+
func checkKeyType(key *CustomerEncryptionKey) error {
126+
switch key.KeyType {
127+
case CustomerManagedKey:
128+
if key.ManagedKey == nil || key.SuppliedKey != nil {
129+
return fmt.Errorf("CustomerEncryptionKey KeyType of Managed requires only ManagedKey to be set")
130+
}
131+
case CustomerSuppliedKey:
132+
if key.SuppliedKey == nil || key.ManagedKey != nil {
133+
return fmt.Errorf("CustomerEncryptionKey KeyType of Supplied requires only SuppliedKey to be set")
134+
}
135+
if len(key.SuppliedKey.RawKey) > 0 && len(key.SuppliedKey.RSAEncryptedKey) > 0 {
136+
return fmt.Errorf("CustomerEncryptionKey KeyType of Supplied requires either RawKey or RSAEncryptedKey to be set, not both")
137+
}
138+
default:
139+
return fmt.Errorf("invalid value for CustomerEncryptionKey KeyType %s", key.KeyType)
140+
}
141+
return nil
142+
}
143+
144+
func validateCustomerEncryptionKey(spec GCPMachineSpec) error {
145+
if spec.RootDiskEncryptionKey != nil {
146+
if err := checkKeyType(spec.RootDiskEncryptionKey); err != nil {
147+
return err
148+
}
149+
}
150+
151+
for _, disk := range spec.AdditionalDisks {
152+
if disk.EncryptionKey != nil {
153+
if err := checkKeyType(disk.EncryptionKey); err != nil {
154+
return err
155+
}
156+
}
157+
}
158+
return nil
159+
}

api/v1beta1/gcpmachine_webhook_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,86 @@ func TestGCPMachine_ValidateCreate(t *testing.T) {
8585
},
8686
wantErr: true,
8787
},
88+
{
89+
name: "GCPMachine with RootDiskEncryptionKey KeyType Managed and Managed field set",
90+
GCPMachine: &GCPMachine{
91+
Spec: GCPMachineSpec{
92+
RootDiskEncryptionKey: &CustomerEncryptionKey{
93+
KeyType: CustomerManagedKey,
94+
ManagedKey: &ManagedKey{
95+
KMSKeyName: "projects/my-project/locations/us-central1/keyRings/us-central1/cryptoKeys/some-key",
96+
},
97+
},
98+
},
99+
},
100+
wantErr: false,
101+
},
102+
{
103+
name: "GCPMachine with RootDiskEncryptionKey KeyType Managed and Managed field not set",
104+
GCPMachine: &GCPMachine{
105+
Spec: GCPMachineSpec{
106+
RootDiskEncryptionKey: &CustomerEncryptionKey{
107+
KeyType: CustomerManagedKey,
108+
},
109+
},
110+
},
111+
wantErr: true,
112+
},
113+
{
114+
name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and Supplied field not set",
115+
GCPMachine: &GCPMachine{
116+
Spec: GCPMachineSpec{
117+
RootDiskEncryptionKey: &CustomerEncryptionKey{
118+
KeyType: CustomerSuppliedKey,
119+
},
120+
},
121+
},
122+
wantErr: true,
123+
},
124+
{
125+
name: "GCPMachine with AdditionalDisk Encryption KeyType Managed and Managed field not set",
126+
GCPMachine: &GCPMachine{
127+
Spec: GCPMachineSpec{
128+
AdditionalDisks: []AttachedDiskSpec{
129+
{
130+
EncryptionKey: &CustomerEncryptionKey{
131+
KeyType: CustomerManagedKey,
132+
},
133+
},
134+
},
135+
},
136+
},
137+
wantErr: true,
138+
},
139+
{
140+
name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and one Supplied field set",
141+
GCPMachine: &GCPMachine{
142+
Spec: GCPMachineSpec{
143+
RootDiskEncryptionKey: &CustomerEncryptionKey{
144+
KeyType: CustomerSuppliedKey,
145+
SuppliedKey: &SuppliedKey{
146+
RawKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
147+
},
148+
},
149+
},
150+
},
151+
wantErr: false,
152+
},
153+
{
154+
name: "GCPMachine with RootDiskEncryptionKey KeyType Supplied and both Supplied fields set",
155+
GCPMachine: &GCPMachine{
156+
Spec: GCPMachineSpec{
157+
RootDiskEncryptionKey: &CustomerEncryptionKey{
158+
KeyType: CustomerSuppliedKey,
159+
SuppliedKey: &SuppliedKey{
160+
RawKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
161+
RSAEncryptedKey: []byte("SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="),
162+
},
163+
},
164+
},
165+
},
166+
wantErr: true,
167+
},
88168
}
89169
for _, test := range tests {
90170
test := test

api/v1beta1/zz_generated.deepcopy.go

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

0 commit comments

Comments
 (0)