diff --git a/WORKSPACE b/WORKSPACE index d92e10044177..0e5d7dbd5918 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -128,11 +128,11 @@ http_file( http_archive( name = "bazeldnf", - sha256 = "6a2af09c6a598a3c4e4fec9af78334fbec2b3c16473f4e2c692fe2e567dc6f56", - strip_prefix = "bazeldnf-0.5.1", + sha256 = "c37709d05ad7eae4d32d7a525f098fd026483ada5e11cdf84d47028222796605", + strip_prefix = "bazeldnf-0.5.2", urls = [ - "https://github.com/rmohr/bazeldnf/archive/v0.5.1.tar.gz", - "https://storage.googleapis.com/builddeps/6a2af09c6a598a3c4e4fec9af78334fbec2b3c16473f4e2c692fe2e567dc6f56", + "https://github.com/rmohr/bazeldnf/archive/v0.5.2.tar.gz", + "https://storage.googleapis.com/builddeps/c37709d05ad7eae4d32d7a525f098fd026483ada5e11cdf84d47028222796605", ], ) @@ -333,9 +333,9 @@ container_pull( container_pull( name = "fedora_with_test_tooling_aarch64", - digest = "sha256:9ec3e137bff093597d192f5a4e346f25b614c3a94216b857de0e3d75b68bfb17", + digest = "sha256:9b1371260c05086a24ac9effdbedca9759c885ea8db93de7f0339df3bcd5a5c3", registry = "quay.io", - repository = "kubevirt/fedora-with-test-tooling", + repository = "kubevirtci/fedora-with-test-tooling", ) container_pull( diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 075b28aecc23..96d205da75da 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -12933,6 +12933,10 @@ "description": "Whether to emulate a sound device.", "$ref": "#/definitions/v1.SoundDevice" }, + "tpm": { + "description": "Whether to emulate a TPM device.", + "$ref": "#/definitions/v1.TPMDevice" + }, "useVirtioTransitional": { "description": "Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is helpful for old machines like CentOS6 or RHEL6 which do not understand virtio_non_transitional (virtio 1.0).", "type": "boolean" @@ -14934,6 +14938,9 @@ } } }, + "v1.TPMDevice": { + "type": "object" + }, "v1.Timer": { "description": "Represents all available timers in a vmi.", "type": "object", diff --git a/cmd/virt-launcher/BUILD.bazel b/cmd/virt-launcher/BUILD.bazel index 76bf01fff626..df99f8efea66 100644 --- a/cmd/virt-launcher/BUILD.bazel +++ b/cmd/virt-launcher/BUILD.bazel @@ -69,6 +69,7 @@ xattrs( capabilities = { "/usr/bin/virt-launcher": [ "cap_net_bind_service", + "cap_sys_ptrace", ], }, selinux_labels = { @@ -110,7 +111,7 @@ group_file( passwd_entry( name = "qemu-user", gid = 107, - home = "", + home = "/home/qemu", shell = "/bin/bash", uid = 107, username = "qemu", @@ -153,6 +154,15 @@ pkg_tar( package_dir = "/etc", ) +pkg_tar( + name = "swtpm-localca-tar", + empty_dirs = [ + "var/lib/swtpm-localca", + ], + mode = "0750", + owner = "107.107", +) + container_image( name = "version-container", directory = "/", @@ -162,12 +172,14 @@ container_image( ":libvirt-config", ":passwd-tar", ":nsswitch-tar", + ":swtpm-localca-tar", "//rpm:launcherbase_aarch64", ], "//conditions:default": [ ":libvirt-config", ":passwd-tar", ":nsswitch-tar", + "swtpm-localca-tar", "//rpm:launcherbase_x86_64", ], }), diff --git a/cmd/virt-launcher/virt-launcher.go b/cmd/virt-launcher/virt-launcher.go index ddfa3a584e87..96f082581cf9 100644 --- a/cmd/virt-launcher/virt-launcher.go +++ b/cmd/virt-launcher/virt-launcher.go @@ -473,7 +473,12 @@ func main() { domain := waitForDomainUUID(*qemuTimeout, events, signalStopChan, domainManager) if domain != nil { - mon := virtlauncher.NewProcessMonitor(domain.Spec.UUID, + // The first argument to NewProcessMonitor will end up being grepped in /proc/*/cmdline + // to find what is hopefully the qemu process. + // If any other process includes that string, it might wrongly be considered. + // FIXME: just read the pidfile libvirt creates for qemu instead of grepping /proc + // See: https://github.com/kubevirt/kubevirt/issues/7067 + mon := virtlauncher.NewProcessMonitor("uuid="+domain.Spec.UUID, *gracePeriodSeconds, finalShutdownCallback, gracefulShutdownCallback) diff --git a/examples/vm-template-windows2012r2.yaml b/examples/vm-template-windows2012r2.yaml index febefdd891ea..f1cf2c95e3dd 100644 --- a/examples/vm-template-windows2012r2.yaml +++ b/examples/vm-template-windows2012r2.yaml @@ -53,6 +53,7 @@ objects: - masquerade: {} model: e1000 name: default + tpm: {} features: acpi: {} apic: {} @@ -61,7 +62,11 @@ objects: spinlocks: spinlocks: 8191 vapic: {} + smm: {} firmware: + bootloader: + efi: + secureBoot: true uuid: 5d307ca9-b3ef-428c-8861-06e72d69f223 resources: requests: diff --git a/examples/vmi-windows.yaml b/examples/vmi-windows.yaml index 2a506add05a1..bf997ae20d7f 100644 --- a/examples/vmi-windows.yaml +++ b/examples/vmi-windows.yaml @@ -28,6 +28,7 @@ spec: - masquerade: {} model: e1000 name: default + tpm: {} features: acpi: {} apic: {} @@ -36,7 +37,11 @@ spec: spinlocks: spinlocks: 8191 vapic: {} + smm: {} firmware: + bootloader: + efi: + secureBoot: true uuid: 5d307ca9-b3ef-428c-8861-06e72d69f223 resources: requests: diff --git a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go index b2678b668ba8..3a7e4f8e8386 100644 --- a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go +++ b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go @@ -1705,7 +1705,7 @@ func validateDomainSpec(field *k8sfield.Path, spec *v1.DomainSpec) []metav1.Stat if spec.Firmware != nil && spec.Firmware.Bootloader != nil && spec.Firmware.Bootloader.EFI != nil && (spec.Firmware.Bootloader.EFI.SecureBoot == nil || *spec.Firmware.Bootloader.EFI.SecureBoot) && - (spec.Features == nil || spec.Features.SMM == nil || !*spec.Features.SMM.Enabled) { + (spec.Features == nil || spec.Features.SMM == nil || (spec.Features.SMM.Enabled != nil && !*spec.Features.SMM.Enabled)) { causes = append(causes, metav1.StatusCause{ Type: metav1.CauseTypeFieldValueInvalid, Message: fmt.Sprintf("%s has EFI SecureBoot enabled. SecureBoot requires SMM, which is currently disabled.", field.String()), diff --git a/pkg/virt-controller/services/template.go b/pkg/virt-controller/services/template.go index 262c9e3b7be4..f0c4048e4680 100644 --- a/pkg/virt-controller/services/template.go +++ b/pkg/virt-controller/services/template.go @@ -86,6 +86,7 @@ const ( CAP_NET_RAW = "NET_RAW" CAP_SYS_ADMIN = "SYS_ADMIN" CAP_SYS_NICE = "SYS_NICE" + CAP_SYS_PTRACE = "SYS_PTRACE" ) // LibvirtStartupDelay is added to custom liveness and readiness probes initial delay value. @@ -1805,20 +1806,25 @@ func haveSlirp(vmi *v1.VirtualMachineInstance) bool { } func getRequiredCapabilities(vmi *v1.VirtualMachineInstance) []k8sv1.Capability { - if util.IsNonRootVMI(vmi) { - return []k8sv1.Capability{CAP_NET_BIND_SERVICE} - } - capabilities := []k8sv1.Capability{} - if requireDHCP(vmi) || haveSlirp(vmi) { + var capabilities []k8sv1.Capability + + if requireDHCP(vmi) || haveSlirp(vmi) || util.IsNonRootVMI(vmi) { capabilities = append(capabilities, CAP_NET_BIND_SERVICE) } - // add a CAP_SYS_NICE capability to allow setting cpu affinity - capabilities = append(capabilities, CAP_SYS_NICE) - // add CAP_SYS_ADMIN capability to allow virtiofs - if util.IsVMIVirtiofsEnabled(vmi) { - capabilities = append(capabilities, CAP_SYS_ADMIN) - capabilities = append(capabilities, getVirtiofsCapabilities()...) + if !util.IsNonRootVMI(vmi) { + // add a CAP_SYS_NICE capability to allow setting cpu affinity + capabilities = append(capabilities, CAP_SYS_NICE) + // add CAP_SYS_ADMIN capability to allow virtiofs + if util.IsVMIVirtiofsEnabled(vmi) { + capabilities = append(capabilities, CAP_SYS_ADMIN) + capabilities = append(capabilities, getVirtiofsCapabilities()...) + } } + // add CAP_SYS_PTRACE capability needed by libvirt + swtpm + // TODO: drop SYS_PTRACE after updating libvirt to a release containing: + // https://github.com/libvirt/libvirt/commit/a9c500d2b50c5c041a1bb6ae9724402cf1cec8fe + capabilities = append(capabilities, CAP_SYS_PTRACE) + return capabilities } @@ -1933,6 +1939,12 @@ func GetMemoryOverhead(vmi *v1.VirtualMachineInstance, cpuArch string) *resource overhead.Add(resource.MustParse("256Mi")) } + // Having a TPM device will spawn a swtpm process + // In `ps`, swtpm has VSZ of 53808 and RSS of 3496, so 53Mi should do + if vmi.Spec.Domain.Devices.TPM != nil { + overhead.Add(resource.MustParse("53Mi")) + } + return overhead } diff --git a/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go b/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go index d04cead49cc1..57638845cc5d 100644 --- a/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go +++ b/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go @@ -739,6 +739,11 @@ func (in *Devices) DeepCopyInto(out *Devices) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.TPMs != nil { + in, out := &in.TPMs, &out.TPMs + *out = make([]TPM, len(*in)) + copy(*out, *in) + } return } @@ -2886,6 +2891,39 @@ func (in *SysInfo) DeepCopy() *SysInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TPM) DeepCopyInto(out *TPM) { + *out = *in + out.Backend = in.Backend + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TPM. +func (in *TPM) DeepCopy() *TPM { + if in == nil { + return nil + } + out := new(TPM) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TPMBackend) DeepCopyInto(out *TPMBackend) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TPMBackend. +func (in *TPMBackend) DeepCopy() *TPMBackend { + if in == nil { + return nil + } + out := new(TPMBackend) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Timer) DeepCopyInto(out *Timer) { *out = *in diff --git a/pkg/virt-launcher/virtwrap/api/schema.go b/pkg/virt-launcher/virtwrap/api/schema.go index 7fd086831ba6..da373ee15ad5 100644 --- a/pkg/virt-launcher/virtwrap/api/schema.go +++ b/pkg/virt-launcher/virtwrap/api/schema.go @@ -459,6 +459,17 @@ type Devices struct { Filesystems []FilesystemDevice `xml:"filesystem,omitempty"` Redirs []RedirectedDevice `xml:"redirdev,omitempty"` SoundCards []SoundCard `xml:"sound,omitempty"` + TPMs []TPM `xml:"tpm,omitempty"` +} + +type TPM struct { + Model string `xml:"model,attr"` + Backend TPMBackend `xml:"backend"` +} + +type TPMBackend struct { + Type string `xml:"type,attr"` + Version string `xml:"version,attr"` } // RedirectedDevice describes a device to be redirected diff --git a/pkg/virt-launcher/virtwrap/converter/converter.go b/pkg/virt-launcher/virtwrap/converter/converter.go index bb5f8197b61e..8f6af1fe986f 100644 --- a/pkg/virt-launcher/virtwrap/converter/converter.go +++ b/pkg/virt-launcher/virtwrap/converter/converter.go @@ -1843,6 +1843,18 @@ func Convert_v1_VirtualMachineInstance_To_api_Domain(vmi *v1.VirtualMachineInsta api.Arg{Value: "isa-debugcon,iobase=0x402,chardev=firmwarelog"}) } + if vmi.Spec.Domain.Devices.TPM != nil { + domain.Spec.Devices.TPMs = []api.TPM{ + { + Model: "tpm-tis", + Backend: api.TPMBackend{ + Type: "emulator", + Version: "2.0", + }, + }, + } + } + return nil } diff --git a/pkg/virt-launcher/virtwrap/util/libvirt_helper.go b/pkg/virt-launcher/virtwrap/util/libvirt_helper.go index f69316fadef5..4800930eaf36 100644 --- a/pkg/virt-launcher/virtwrap/util/libvirt_helper.go +++ b/pkg/virt-launcher/virtwrap/util/libvirt_helper.go @@ -238,7 +238,7 @@ func (l LibvirtWrapper) StartLibvirt(stopChan chan struct{}) { cmd := exec.Command("/usr/sbin/libvirtd", args...) if l.user != 0 { cmd.SysProcAttr = &syscall.SysProcAttr{ - AmbientCaps: []uintptr{unix.CAP_NET_BIND_SERVICE}, + AmbientCaps: []uintptr{unix.CAP_NET_BIND_SERVICE, unix.CAP_SYS_PTRACE}, } } diff --git a/pkg/virt-operator/resource/generate/components/scc.go b/pkg/virt-operator/resource/generate/components/scc.go index 039504a40540..32775babad86 100644 --- a/pkg/virt-operator/resource/generate/components/scc.go +++ b/pkg/virt-operator/resource/generate/components/scc.go @@ -75,10 +75,12 @@ func NewKubeVirtControllerSCC(namespace string) *secv1.SecurityContextConstraint Type: secv1.SELinuxStrategyRunAsAny, } scc.AllowedCapabilities = []corev1.Capability{ - // add a CAP_SYS_NICE capability to allow setting cpu affinity + // add CAP_SYS_NICE capability to allow setting cpu affinity "SYS_NICE", // add CAP_NET_BIND_SERVICE capability to allow dhcp and slirp operations "NET_BIND_SERVICE", + // add CAP_SYS_PTRACE capability needed for libvirt <8.1.0 to find the pid of swtpm + "SYS_PTRACE", } scc.AllowHostDirVolumePlugin = true scc.Users = []string{fmt.Sprintf("system:serviceaccount:%s:kubevirt-controller", namespace)} diff --git a/pkg/virt-operator/resource/generate/components/validations_generated.go b/pkg/virt-operator/resource/generate/components/validations_generated.go index 3e9589517f99..e4d007b570db 100644 --- a/pkg/virt-operator/resource/generate/components/validations_generated.go +++ b/pkg/virt-operator/resource/generate/components/validations_generated.go @@ -5286,6 +5286,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is helpful for old machines @@ -8523,6 +8526,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is helpful for old machines like @@ -10644,6 +10650,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is helpful for old machines like @@ -12727,6 +12736,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is helpful for old machines @@ -16356,6 +16368,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. This is @@ -20355,6 +20370,9 @@ var CRDsValidation map[string]string = map[string]string{ required: - name type: object + tpm: + description: Whether to emulate a TPM device. + type: object useVirtioTransitional: description: Fall back to legacy virtio 0.9 support if virtio bus is selected on devices. diff --git a/staging/src/kubevirt.io/api/core/v1/deepcopy_generated.go b/staging/src/kubevirt.io/api/core/v1/deepcopy_generated.go index 1e95b2708bfb..f73196e32e40 100644 --- a/staging/src/kubevirt.io/api/core/v1/deepcopy_generated.go +++ b/staging/src/kubevirt.io/api/core/v1/deepcopy_generated.go @@ -853,6 +853,11 @@ func (in *Devices) DeepCopyInto(out *Devices) { *out = new(SoundDevice) **out = **in } + if in.TPM != nil { + in, out := &in.TPM, &out.TPM + *out = new(TPMDevice) + **out = **in + } return } @@ -3464,6 +3469,22 @@ func (in *SysprepSource) DeepCopy() *SysprepSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TPMDevice) DeepCopyInto(out *TPMDevice) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TPMDevice. +func (in *TPMDevice) DeepCopy() *TPMDevice { + if in == nil { + return nil + } + out := new(TPMDevice) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Timer) DeepCopyInto(out *Timer) { *out = *in diff --git a/staging/src/kubevirt.io/api/core/v1/schema.go b/staging/src/kubevirt.io/api/core/v1/schema.go index 0724688249bd..358cd1f47a41 100644 --- a/staging/src/kubevirt.io/api/core/v1/schema.go +++ b/staging/src/kubevirt.io/api/core/v1/schema.go @@ -453,6 +453,9 @@ type Devices struct { // Whether to emulate a sound device. // +optional Sound *SoundDevice `json:"sound,omitempty"` + // Whether to emulate a TPM device. + // +optional + TPM *TPMDevice `json:"tpm,omitempty"` } // Represent a subset of client devices that can be accessed by VMI. At the @@ -481,6 +484,8 @@ type SoundDevice struct { Model string `json:"model,omitempty"` } +type TPMDevice struct{} + type Input struct { // Bus indicates the bus of input device to emulate. // Supported values: virtio, usb. diff --git a/staging/src/kubevirt.io/api/core/v1/schema_swagger_generated.go b/staging/src/kubevirt.io/api/core/v1/schema_swagger_generated.go index faa5c1db30ba..76829720a7d7 100644 --- a/staging/src/kubevirt.io/api/core/v1/schema_swagger_generated.go +++ b/staging/src/kubevirt.io/api/core/v1/schema_swagger_generated.go @@ -245,6 +245,7 @@ func (Devices) SwaggerDoc() map[string]string { "hostDevices": "Whether to attach a host device to the vmi.\n+optional\n+listType=atomic", "clientPassthrough": "To configure and access client devices such as redirecting USB\n+optional", "sound": "Whether to emulate a sound device.\n+optional", + "tpm": "Whether to emulate a TPM device.\n+optional", } } @@ -262,6 +263,10 @@ func (SoundDevice) SwaggerDoc() map[string]string { } } +func (TPMDevice) SwaggerDoc() map[string]string { + return map[string]string{} +} + func (Input) SwaggerDoc() map[string]string { return map[string]string{ "bus": "Bus indicates the bus of input device to emulate.\nSupported values: virtio, usb.", diff --git a/staging/src/kubevirt.io/client-go/api/openapi_generated.go b/staging/src/kubevirt.io/client-go/api/openapi_generated.go index 27cc4e7d2b8f..c7f1c74bbd15 100644 --- a/staging/src/kubevirt.io/client-go/api/openapi_generated.go +++ b/staging/src/kubevirt.io/client-go/api/openapi_generated.go @@ -436,6 +436,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "kubevirt.io/api/core/v1.StopOptions": schema_kubevirtio_api_core_v1_StopOptions(ref), "kubevirt.io/api/core/v1.SyNICTimer": schema_kubevirtio_api_core_v1_SyNICTimer(ref), "kubevirt.io/api/core/v1.SysprepSource": schema_kubevirtio_api_core_v1_SysprepSource(ref), + "kubevirt.io/api/core/v1.TPMDevice": schema_kubevirtio_api_core_v1_TPMDevice(ref), "kubevirt.io/api/core/v1.Timer": schema_kubevirtio_api_core_v1_Timer(ref), "kubevirt.io/api/core/v1.TokenBucketRateLimiter": schema_kubevirtio_api_core_v1_TokenBucketRateLimiter(ref), "kubevirt.io/api/core/v1.TopologyHints": schema_kubevirtio_api_core_v1_TopologyHints(ref), @@ -15458,11 +15459,17 @@ func schema_kubevirtio_api_core_v1_Devices(ref common.ReferenceCallback) common. Ref: ref("kubevirt.io/api/core/v1.SoundDevice"), }, }, + "tpm": { + SchemaProps: spec.SchemaProps{ + Description: "Whether to emulate a TPM device.", + Ref: ref("kubevirt.io/api/core/v1.TPMDevice"), + }, + }, }, }, }, Dependencies: []string{ - "kubevirt.io/api/core/v1.ClientPassthroughDevices", "kubevirt.io/api/core/v1.Disk", "kubevirt.io/api/core/v1.Filesystem", "kubevirt.io/api/core/v1.GPU", "kubevirt.io/api/core/v1.HostDevice", "kubevirt.io/api/core/v1.Input", "kubevirt.io/api/core/v1.Interface", "kubevirt.io/api/core/v1.Rng", "kubevirt.io/api/core/v1.SoundDevice", "kubevirt.io/api/core/v1.Watchdog"}, + "kubevirt.io/api/core/v1.ClientPassthroughDevices", "kubevirt.io/api/core/v1.Disk", "kubevirt.io/api/core/v1.Filesystem", "kubevirt.io/api/core/v1.GPU", "kubevirt.io/api/core/v1.HostDevice", "kubevirt.io/api/core/v1.Input", "kubevirt.io/api/core/v1.Interface", "kubevirt.io/api/core/v1.Rng", "kubevirt.io/api/core/v1.SoundDevice", "kubevirt.io/api/core/v1.TPMDevice", "kubevirt.io/api/core/v1.Watchdog"}, } } @@ -19379,6 +19386,16 @@ func schema_kubevirtio_api_core_v1_SysprepSource(ref common.ReferenceCallback) c } } +func schema_kubevirtio_api_core_v1_TPMDevice(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + }, + }, + } +} + func schema_kubevirtio_api_core_v1_Timer(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index d43dffe22d4a..88f40f4227ba 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -165,6 +165,7 @@ go_test( "vmi_monitoring_test.go", "vmi_multiqueue_test.go", "vmi_sound_test.go", + "vmi_tpm_test.go", "vmidefaults_test.go", "vmipreset_test.go", "vnc_test.go", diff --git a/tests/security_features_test.go b/tests/security_features_test.go index 2eda3cc89797..f92c67d1d1b5 100644 --- a/tests/security_features_test.go +++ b/tests/security_features_test.go @@ -239,21 +239,21 @@ var _ = Describe("[Serial][sig-compute]SecurityFeatures", func() { } caps := *container.SecurityContext.Capabilities if checks.HasFeature(virtconfig.NonRoot) { - Expect(caps.Add).To(HaveLen(1), fmt.Sprintf("Expecting to found \"NET_BIND_SERVICE\". Found capabilities %s", caps.Add)) - Expect(caps.Add[0]).To(Equal(k8sv1.Capability("NET_BIND_SERVICE"))) + Expect(caps.Add).To(HaveLen(2), fmt.Sprintf("Found capabilities %s, expected NET_BIND_SERVICE and SYS_PTRACE", caps.Add)) + Expect(caps.Add).To(ContainElements(k8sv1.Capability("NET_BIND_SERVICE"), k8sv1.Capability("SYS_PTRACE"))) } else { - Expect(caps.Add).To(HaveLen(2), fmt.Sprintf("Found capabilities %s, expecting SYS_NICE and NET_BIND_SERVICE ", caps.Add)) - Expect(caps.Add).To(ContainElements(k8sv1.Capability("NET_BIND_SERVICE"), k8sv1.Capability("SYS_NICE"))) + Expect(caps.Add).To(HaveLen(3), fmt.Sprintf("Found capabilities %s, expected NET_BIND_SERVICE, SYS_NICE and SYS_PTRACE", caps.Add)) + Expect(caps.Add).To(ContainElements(k8sv1.Capability("NET_BIND_SERVICE"), k8sv1.Capability("SYS_NICE"), k8sv1.Capability("SYS_PTRACE"))) } By("Checking virt-launcher Pod's compute container has precisely the documented extra capabilities") - for _, cap := range caps.Add { - Expect(tests.IsLauncherCapabilityValid(cap)).To(BeTrue(), "Expected compute container of virt_launcher to be granted only specific capabilities") + for _, capa := range caps.Add { + Expect(tests.IsLauncherCapabilityValid(capa)).To(BeTrue(), "Expected compute container of virt_launcher to be granted only specific capabilities") } By("Checking virt-launcher Pod's compute container has precisely the documented dropped capabilities") Expect(caps.Drop).To(HaveLen(1)) - for _, cap := range caps.Drop { - Expect(tests.IsLauncherCapabilityDropped(cap)).To(BeTrue(), "Expected compute container of virt_launcher to drop only specific capabilities") + for _, capa := range caps.Drop { + Expect(tests.IsLauncherCapabilityDropped(capa)).To(BeTrue(), "Expected compute container of virt_launcher to drop only specific capabilities") } }) }) diff --git a/tests/utils.go b/tests/utils.go index 2f7225110e00..e4d868c522fa 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -210,6 +210,7 @@ const ( const ( capNetRaw k8sv1.Capability = "NET_RAW" capSysNice k8sv1.Capability = "SYS_NICE" + capSysPTrace k8sv1.Capability = "SYS_PTRACE" capNetBindService k8sv1.Capability = "NET_BIND_SERVICE" ) @@ -4414,7 +4415,8 @@ func IsLauncherCapabilityValid(capability k8sv1.Capability) bool { switch capability { case capNetBindService, - capSysNice: + capSysNice, + capSysPTrace: return true } return false diff --git a/tests/vmi_tpm_test.go b/tests/vmi_tpm_test.go new file mode 100644 index 000000000000..abfb3e655a3e --- /dev/null +++ b/tests/vmi_tpm_test.go @@ -0,0 +1,85 @@ +/* + * This file is part of the KubeVirt project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2022 Red Hat, Inc. + * + */ + +package tests_test + +import ( + expect "github.com/google/goexpect" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "kubevirt.io/kubevirt/tests/framework/checks" + + v1 "kubevirt.io/api/core/v1" + + "kubevirt.io/client-go/kubecli" + + "kubevirt.io/kubevirt/tests" + "kubevirt.io/kubevirt/tests/console" +) + +var _ = Describe("[sig-compute]vTPM", func() { + var virtClient kubecli.KubevirtClient + var err error + + BeforeEach(func() { + virtClient, err = kubecli.GetKubevirtClient() + Expect(err).ToNot(HaveOccurred()) + }) + + Context("with TPM VMI option enabled", func() { + It("should expose a functional emulated TPM which persists across migrations", func() { + By("Creating a VMI with TPM enabled") + vmi := tests.NewRandomFedoraVMI() + vmi.Spec.Domain.Devices.TPM = &v1.TPMDevice{} + vmi = tests.RunVMIAndExpectLaunch(vmi, 60) + + By("Logging in as root") + err = console.LoginToFedora(vmi) + Expect(err).ToNot(HaveOccurred()) + + By("Ensuring a TPM device is present") + Expect(console.SafeExpectBatch(vmi, []expect.Batcher{ + &expect.BSnd{S: "ls /dev/tpm*\n"}, + &expect.BExp{R: "/dev/tpm0"}, + }, 300)).To(Succeed(), "Could not find a TPM device") + + By("Ensuring the TPM device is functional") + Expect(console.SafeExpectBatch(vmi, []expect.Batcher{ + &expect.BSnd{S: "tpm2_pcrread sha256:15\n"}, + &expect.BExp{R: "0x0000000000000000000000000000000000000000000000000000000000000000"}, + &expect.BSnd{S: "tpm2_pcrextend 15:sha256=54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c && echo 'do''ne'\n"}, + &expect.BExp{R: "done"}, + &expect.BSnd{S: "tpm2_pcrread sha256:15\n"}, + &expect.BExp{R: "0x1EE66777C372B96BC74AC4CB892E0879FA3CCF6A2F53DB1D00FD18B264797F49"}, + }, 300)).To(Succeed(), "PCR extension doesn't work correctly") + + By("Migrating the VMI") + checks.SkipIfMigrationIsNotPossible() + migration := tests.NewRandomMigration(vmi.Name, vmi.Namespace) + tests.RunMigrationAndExpectCompletion(virtClient, migration, tests.MigrationWaitTime) + + By("Ensuring the TPM is still functional and its state carried over") + Expect(console.SafeExpectBatch(vmi, []expect.Batcher{ + &expect.BSnd{S: "tpm2_pcrread sha256:15\n"}, + &expect.BExp{R: "0x1EE66777C372B96BC74AC4CB892E0879FA3CCF6A2F53DB1D00FD18B264797F49"}, + }, 300)).To(Succeed(), "Migrating broke the TPM") + }) + }) +}) diff --git a/tools/vms-generator/utils/utils.go b/tools/vms-generator/utils/utils.go index 5220d634f329..431896e6c9df 100644 --- a/tools/vms-generator/utils/utils.go +++ b/tools/vms-generator/utils/utils.go @@ -577,6 +577,7 @@ func GetVMIWindows() *v1.VirtualMachineInstance { gracePeriod := int64(0) spinlocks := uint32(8191) firmware := types.UID(windowsFirmware) + _true := true _false := false vmi.Spec = v1.VirtualMachineInstanceSpec{ TerminationGracePeriodSeconds: &gracePeriod, @@ -590,6 +591,7 @@ func GetVMIWindows() *v1.VirtualMachineInstance { VAPIC: &v1.FeatureState{}, Spinlocks: &v1.FeatureSpinlocks{Retries: &spinlocks}, }, + SMM: &v1.FeatureState{}, }, Clock: &v1.Clock{ ClockOffset: v1.ClockOffset{UTC: &v1.ClockOffsetUTC{}}, @@ -600,7 +602,12 @@ func GetVMIWindows() *v1.VirtualMachineInstance { Hyperv: &v1.HypervTimer{}, }, }, - Firmware: &v1.Firmware{UUID: firmware}, + Firmware: &v1.Firmware{ + UUID: firmware, + Bootloader: &v1.Bootloader{ + EFI: &v1.EFI{SecureBoot: &_true}, + }, + }, Resources: v1.ResourceRequirements{ Requests: k8sv1.ResourceList{ k8sv1.ResourceMemory: resource.MustParse("2048Mi"), @@ -608,6 +615,7 @@ func GetVMIWindows() *v1.VirtualMachineInstance { }, Devices: v1.Devices{ Interfaces: []v1.Interface{*v1.DefaultMasqueradeNetworkInterface()}, + TPM: &v1.TPMDevice{}, }, }, Networks: []v1.Network{*v1.DefaultPodNetwork()},