diff --git a/api/v1alpha3/gcpmachine_conversion.go b/api/v1alpha3/gcpmachine_conversion.go index deaf06fed..3a875d65e 100644 --- a/api/v1alpha3/gcpmachine_conversion.go +++ b/api/v1alpha3/gcpmachine_conversion.go @@ -42,6 +42,10 @@ func (src *GCPMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint dst.Spec.IPForwarding = restored.Spec.IPForwarding } + if restored.Spec.ShieldedInstanceConfig != nil { + dst.Spec.ShieldedInstanceConfig = restored.Spec.ShieldedInstanceConfig + } + return nil } diff --git a/api/v1alpha3/gcpmachinetemplate_conversion.go b/api/v1alpha3/gcpmachinetemplate_conversion.go index d48623d25..9590af025 100644 --- a/api/v1alpha3/gcpmachinetemplate_conversion.go +++ b/api/v1alpha3/gcpmachinetemplate_conversion.go @@ -42,6 +42,10 @@ func (src *GCPMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolin dst.Spec.Template.Spec.IPForwarding = restored.Spec.Template.Spec.IPForwarding } + if restored.Spec.Template.Spec.ShieldedInstanceConfig != nil { + dst.Spec.Template.Spec.ShieldedInstanceConfig = restored.Spec.Template.Spec.ShieldedInstanceConfig + } + return nil } diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go index 02a3fa6ed..7580b5881 100644 --- a/api/v1alpha3/zz_generated.conversion.go +++ b/api/v1alpha3/zz_generated.conversion.go @@ -572,6 +572,7 @@ func autoConvert_v1beta1_GCPMachineSpec_To_v1alpha3_GCPMachineSpec(in *v1beta1.G out.ServiceAccount = (*ServiceAccount)(unsafe.Pointer(in.ServiceAccount)) out.Preemptible = in.Preemptible // WARNING: in.IPForwarding requires manual conversion: does not exist in peer-type + // WARNING: in.ShieldedInstanceConfig requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1alpha4/gcpmachine_conversion.go b/api/v1alpha4/gcpmachine_conversion.go index d908a00f2..3eee5dfd7 100644 --- a/api/v1alpha4/gcpmachine_conversion.go +++ b/api/v1alpha4/gcpmachine_conversion.go @@ -41,6 +41,10 @@ func (src *GCPMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint dst.Spec.IPForwarding = restored.Spec.IPForwarding } + if restored.Spec.ShieldedInstanceConfig != nil { + dst.Spec.ShieldedInstanceConfig = restored.Spec.ShieldedInstanceConfig + } + return nil } diff --git a/api/v1alpha4/gcpmachinetemplate_conversion.go b/api/v1alpha4/gcpmachinetemplate_conversion.go index 783fd2b1d..d618e2feb 100644 --- a/api/v1alpha4/gcpmachinetemplate_conversion.go +++ b/api/v1alpha4/gcpmachinetemplate_conversion.go @@ -43,6 +43,10 @@ func (src *GCPMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { // nolin dst.Spec.Template.Spec.IPForwarding = restored.Spec.Template.Spec.IPForwarding } + if restored.Spec.Template.Spec.ShieldedInstanceConfig != nil { + dst.Spec.Template.Spec.ShieldedInstanceConfig = restored.Spec.Template.Spec.ShieldedInstanceConfig + } + return nil } diff --git a/api/v1alpha4/zz_generated.conversion.go b/api/v1alpha4/zz_generated.conversion.go index d508836e1..ca582ca65 100644 --- a/api/v1alpha4/zz_generated.conversion.go +++ b/api/v1alpha4/zz_generated.conversion.go @@ -744,6 +744,7 @@ func autoConvert_v1beta1_GCPMachineSpec_To_v1alpha4_GCPMachineSpec(in *v1beta1.G out.ServiceAccount = (*ServiceAccount)(unsafe.Pointer(in.ServiceAccount)) out.Preemptible = in.Preemptible // WARNING: in.IPForwarding requires manual conversion: does not exist in peer-type + // WARNING: in.ShieldedInstanceConfig requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1beta1/gcpmachine_types.go b/api/v1beta1/gcpmachine_types.go index 3b9f9cf27..954717222 100644 --- a/api/v1beta1/gcpmachine_types.go +++ b/api/v1beta1/gcpmachine_types.go @@ -66,6 +66,62 @@ const ( IPForwardingDisabled IPForwarding = "Disabled" ) +// SecureBootPolicy represents the secure boot configuration for the GCP machine. +type SecureBootPolicy string + +const ( + // SecureBootPolicyEnabled enables the secure boot configuration for the GCP machine. + SecureBootPolicyEnabled SecureBootPolicy = "Enabled" + // SecureBootPolicyDisabled disables the secure boot configuration for the GCP machine. + SecureBootPolicyDisabled SecureBootPolicy = "Disabled" +) + +// VirtualizedTrustedPlatformModulePolicy represents the virtualized trusted platform module configuration for the GCP machine. +type VirtualizedTrustedPlatformModulePolicy string + +const ( + // VirtualizedTrustedPlatformModulePolicyEnabled enables the virtualized trusted platform module configuration for the GCP machine. + VirtualizedTrustedPlatformModulePolicyEnabled VirtualizedTrustedPlatformModulePolicy = "Enabled" + // VirtualizedTrustedPlatformModulePolicyDisabled disables the virtualized trusted platform module configuration for the GCP machine. + VirtualizedTrustedPlatformModulePolicyDisabled VirtualizedTrustedPlatformModulePolicy = "Disabled" +) + +// IntegrityMonitoringPolicy represents the integrity monitoring configuration for the GCP machine. +type IntegrityMonitoringPolicy string + +const ( + // IntegrityMonitoringPolicyEnabled enables integrity monitoring for the GCP machine. + IntegrityMonitoringPolicyEnabled IntegrityMonitoringPolicy = "Enabled" + // IntegrityMonitoringPolicyDisabled disables integrity monitoring for the GCP machine. + IntegrityMonitoringPolicyDisabled IntegrityMonitoringPolicy = "Disabled" +) + +// GCPShieldedInstanceConfig describes the shielded VM configuration of the instance on GCP. +// Shielded VM configuration allow users to enable and disable Secure Boot, vTPM, and Integrity Monitoring. +type GCPShieldedInstanceConfig struct { + // SecureBoot Defines whether the instance should have secure boot enabled. + // Secure Boot verify the digital signature of all boot components, and halting the boot process if signature verification fails. + // If omitted, the platform chooses a default, which is subject to change over time, currently that default is Disabled. + // +kubebuilder:validation:Enum=Enabled;Disabled + //+optional + SecureBoot SecureBootPolicy `json:"secureBoot,omitempty"` + + // VirtualizedTrustedPlatformModule enable virtualized trusted platform module measurements to create a known good boot integrity policy baseline. + // The integrity policy baseline is used for comparison with measurements from subsequent VM boots to determine if anything has changed. + // If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + // +kubebuilder:validation:Enum=Enabled;Disabled + // +optional + VirtualizedTrustedPlatformModule VirtualizedTrustedPlatformModulePolicy `json:"virtualizedTrustedPlatformModule,omitempty"` + + // IntegrityMonitoring determines whether the instance should have integrity monitoring that verify the runtime boot integrity. + // Compares the most recent boot measurements to the integrity policy baseline and return + // a pair of pass/fail results depending on whether they match or not. + // If omitted, the platform chooses a default, which is subject to change over time, currently that default is Enabled. + // +kubebuilder:validation:Enum=Enabled;Disabled + // +optional + IntegrityMonitoring IntegrityMonitoringPolicy `json:"integrityMonitoring,omitempty"` +} + // GCPMachineSpec defines the desired state of GCPMachine. type GCPMachineSpec struct { // InstanceType is the type of instance to create. Example: n1.standard-2 @@ -149,6 +205,10 @@ type GCPMachineSpec struct { // +kubebuilder:default=Enabled // +optional IPForwarding *IPForwarding `json:"ipForwarding,omitempty"` + + // ShieldedInstanceConfig is the Shielded VM configuration for this machine + // +optional + ShieldedInstanceConfig *GCPShieldedInstanceConfig `json:"shieldedInstanceConfig,omitempty"` } // MetadataItem defines a single piece of metadata associated with an instance. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 89e9b9219..c030b2efa 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -430,6 +430,11 @@ func (in *GCPMachineSpec) DeepCopyInto(out *GCPMachineSpec) { *out = new(IPForwarding) **out = **in } + if in.ShieldedInstanceConfig != nil { + in, out := &in.ShieldedInstanceConfig, &out.ShieldedInstanceConfig + *out = new(GCPShieldedInstanceConfig) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPMachineSpec. @@ -568,6 +573,21 @@ func (in *GCPMachineTemplateSpec) DeepCopy() *GCPMachineTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPShieldedInstanceConfig) DeepCopyInto(out *GCPShieldedInstanceConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPShieldedInstanceConfig. +func (in *GCPShieldedInstanceConfig) DeepCopy() *GCPShieldedInstanceConfig { + if in == nil { + return nil + } + out := new(GCPShieldedInstanceConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in Labels) DeepCopyInto(out *Labels) { { diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go index cdf9f839a..0705421ba 100644 --- a/cloud/scope/machine.go +++ b/cloud/scope/machine.go @@ -351,6 +351,22 @@ func (m *MachineScope) InstanceSpec() *compute.Instance { if m.GCPMachine.Spec.IPForwarding != nil && *m.GCPMachine.Spec.IPForwarding == infrav1.IPForwardingDisabled { instance.CanIpForward = false } + if m.GCPMachine.Spec.ShieldedInstanceConfig != nil { + instance.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{ + EnableSecureBoot: false, + EnableVtpm: true, + EnableIntegrityMonitoring: true, + } + if m.GCPMachine.Spec.ShieldedInstanceConfig.SecureBoot == infrav1.SecureBootPolicyEnabled { + instance.ShieldedInstanceConfig.EnableSecureBoot = true + } + if m.GCPMachine.Spec.ShieldedInstanceConfig.VirtualizedTrustedPlatformModule == infrav1.VirtualizedTrustedPlatformModulePolicyDisabled { + instance.ShieldedInstanceConfig.EnableVtpm = false + } + if m.GCPMachine.Spec.ShieldedInstanceConfig.IntegrityMonitoring == infrav1.IntegrityMonitoringPolicyDisabled { + instance.ShieldedInstanceConfig.EnableIntegrityMonitoring = false + } + } instance.Disks = append(instance.Disks, m.InstanceImageSpec()) instance.Disks = append(instance.Disks, m.InstanceAdditionalDiskSpec()...) diff --git a/cloud/services/compute/instances/reconcile_test.go b/cloud/services/compute/instances/reconcile_test.go index d9d2cc9f6..db7f00dcd 100644 --- a/cloud/services/compute/instances/reconcile_test.go +++ b/cloud/services/compute/instances/reconcile_test.go @@ -115,18 +115,22 @@ var fakeGCPCluster = &infrav1.GCPCluster{ }, } -var fakeGCPMachine = &infrav1.GCPMachine{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-machine", - Namespace: "default", - }, - Spec: infrav1.GCPMachineSpec{ - AdditionalLabels: map[string]string{ - "foo": "bar", +func getFakeGCPMachine() *infrav1.GCPMachine { + return &infrav1.GCPMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-machine", + Namespace: "default", }, - }, + Spec: infrav1.GCPMachineSpec{ + AdditionalLabels: map[string]string{ + "foo": "bar", + }, + }, + } } +var fakeGCPMachine = getFakeGCPMachine() + func TestService_createOrGetInstance(t *testing.T) { fakec := fake.NewClientBuilder(). WithScheme(scheme.Scheme). @@ -271,6 +275,7 @@ func TestService_createOrGetInstance(t *testing.T) { name: "instance does not exist (should create instance) and ipForwarding disabled", scope: func() Scope { ipForwardingDisabled := infrav1.IPForwardingDisabled + machineScope.GCPMachine = getFakeGCPMachine() machineScope.GCPMachine.Spec.IPForwarding = &ipForwardingDisabled return machineScope }, @@ -327,6 +332,69 @@ func TestService_createOrGetInstance(t *testing.T) { Zone: "us-central1-c", }, }, + { + name: "instance does not exist (should create instance) and SecureBoot enabled", + scope: func() Scope { + machineScope.GCPMachine = getFakeGCPMachine() + machineScope.GCPMachine.Spec.ShieldedInstanceConfig = &infrav1.GCPShieldedInstanceConfig{ + SecureBoot: infrav1.SecureBootPolicyEnabled, + } + 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, + ShieldedInstanceConfig: &compute.ShieldedInstanceConfig{EnableSecureBoot: true, EnableVtpm: true, EnableIntegrityMonitoring: 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", + }, + }, + }, + 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: pointer.String("Zm9vCg=="), + }, + }, + }, + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "projects/my-proj/global/networks/default", + }, + }, + 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: "FailureDomain not given (should pick up a failure domain from the cluster)", scope: func() Scope { return machineScopeWithoutFailureDomain }, @@ -337,7 +405,7 @@ func TestService_createOrGetInstance(t *testing.T) { wantErr: false, want: &compute.Instance{ Name: "my-machine", - CanIpForward: false, + CanIpForward: true, Disks: []*compute.AttachedDisk{ { AutoDelete: true, diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml index ae9f7d374..ab9ab9de8 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachines.yaml @@ -607,6 +607,46 @@ spec: type: string type: array type: object + shieldedInstanceConfig: + description: ShieldedInstanceConfig is the Shielded VM configuration + for this machine + properties: + integrityMonitoring: + description: IntegrityMonitoring determines whether the instance + should have integrity monitoring that verify the runtime boot + integrity. Compares the most recent boot measurements to the + integrity policy baseline and return a pair of pass/fail results + depending on whether they match or not. If omitted, the platform + chooses a default, which is subject to change over time, currently + that default is Enabled. + enum: + - Enabled + - Disabled + type: string + secureBoot: + description: SecureBoot Defines whether the instance should have + secure boot enabled. Secure Boot verify the digital signature + of all boot components, and halting the boot process if signature + verification fails. If omitted, the platform chooses a default, + which is subject to change over time, currently that default + is Disabled. + enum: + - Enabled + - Disabled + type: string + virtualizedTrustedPlatformModule: + description: VirtualizedTrustedPlatformModule enable virtualized + trusted platform module measurements to create a known good + boot integrity policy baseline. The integrity policy baseline + is used for comparison with measurements from subsequent VM + boots to determine if anything has changed. If omitted, the + platform chooses a default, which is subject to change over + time, currently that default is Enabled. + enum: + - Enabled + - Disabled + type: string + type: object subnet: description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork retrieved from diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml index 49f0436c9..86cd02b1d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmachinetemplates.yaml @@ -499,6 +499,48 @@ spec: type: string type: array type: object + shieldedInstanceConfig: + description: ShieldedInstanceConfig is the Shielded VM configuration + for this machine + properties: + integrityMonitoring: + description: IntegrityMonitoring determines whether the + instance should have integrity monitoring that verify + the runtime boot integrity. Compares the most recent + boot measurements to the integrity policy baseline and + return a pair of pass/fail results depending on whether + they match or not. If omitted, the platform chooses + a default, which is subject to change over time, currently + that default is Enabled. + enum: + - Enabled + - Disabled + type: string + secureBoot: + description: SecureBoot Defines whether the instance should + have secure boot enabled. Secure Boot verify the digital + signature of all boot components, and halting the boot + process if signature verification fails. If omitted, + the platform chooses a default, which is subject to + change over time, currently that default is Disabled. + enum: + - Enabled + - Disabled + type: string + virtualizedTrustedPlatformModule: + description: VirtualizedTrustedPlatformModule enable virtualized + trusted platform module measurements to create a known + good boot integrity policy baseline. The integrity policy + baseline is used for comparison with measurements from + subsequent VM boots to determine if anything has changed. + If omitted, the platform chooses a default, which is + subject to change over time, currently that default + is Enabled. + enum: + - Enabled + - Disabled + type: string + type: object subnet: description: Subnet is a reference to the subnetwork to use for this instance. If not specified, the first subnetwork