Skip to content

Commit be17146

Browse files
authored
Merge pull request #1137 from bfournie/disk-encryption
Add support for disk encryption key in GCPMachine
2 parents ce36a0e + 0c780af commit be17146

8 files changed

+824
-5
lines changed

api/v1beta1/gcpmachine_types.go

+73
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

+41-1
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

+80
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

+80
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)