Skip to content

Commit

Permalink
Merge pull request #1137 from bfournie/disk-encryption
Browse files Browse the repository at this point in the history
Add support for disk encryption key in GCPMachine
  • Loading branch information
k8s-ci-robot authored Feb 28, 2024
2 parents ce36a0e + 0c780af commit be17146
Show file tree
Hide file tree
Showing 8 changed files with 824 additions and 5 deletions.
73 changes: 73 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,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
Expand Down Expand Up @@ -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.
Expand Down
42 changes: 41 additions & 1 deletion api/v1beta1/gcpmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
80 changes: 80 additions & 0 deletions api/v1beta1/gcpmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 80 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.

Loading

0 comments on commit be17146

Please sign in to comment.