From dfa4d56aa3c1235a067bf0742ebca47fe0004ad9 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Tue, 24 Sep 2024 16:32:04 +0530 Subject: [PATCH 1/4] Add security profile feature in libvirt --- lisa/sut_orchestrator/libvirt/context.py | 1 + lisa/sut_orchestrator/libvirt/features.py | 57 +++++++++++++++++++ lisa/sut_orchestrator/libvirt/platform.py | 68 ++++++++++++++++++++++- lisa/util/constants.py | 2 + 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 lisa/sut_orchestrator/libvirt/features.py diff --git a/lisa/sut_orchestrator/libvirt/context.py b/lisa/sut_orchestrator/libvirt/context.py index 6e6793ae52..0216652fad 100644 --- a/lisa/sut_orchestrator/libvirt/context.py +++ b/lisa/sut_orchestrator/libvirt/context.py @@ -48,6 +48,7 @@ class NodeContext: vm_name: str = "" firmware_source_path: str = "" firmware_path: str = "" + guest_vm_type: str = "" cloud_init_file_path: str = "" ignition_file_path: str = "" os_disk_source_file_path: Optional[str] = None diff --git a/lisa/sut_orchestrator/libvirt/features.py b/lisa/sut_orchestrator/libvirt/features.py new file mode 100644 index 0000000000..7a963f9b8a --- /dev/null +++ b/lisa/sut_orchestrator/libvirt/features.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +from typing import Any, Type, cast + +from dataclasses_json import dataclass_json + +from lisa import features, schema, search_space +from lisa.environment import Environment +from lisa.features.security_profile import SecurityProfileType +from lisa.sut_orchestrator.libvirt.context import get_node_context + + +@dataclass_json() +@dataclass() +class SecurityProfileSettings(features.SecurityProfileSettings): + def __hash__(self) -> int: + return hash(self._get_key()) + + def _get_key(self) -> str: + return f"{self.type}/{self.security_profile}/" + + def _call_requirement_method( + self, method: search_space.RequirementMethod, capability: Any + ) -> Any: + super_value: SecurityProfileSettings = super()._call_requirement_method( + method, capability + ) + value = SecurityProfileSettings() + value.security_profile = super_value.security_profile + return value + + +class SecurityProfile(features.SecurityProfile): + _security_profile_mapping = { + SecurityProfileType.Standard: "", + SecurityProfileType.CVM: "ConfidentialVM", + } + + def _initialize(self, *args: Any, **kwargs: Any) -> None: + super()._initialize(*args, **kwargs) + + @classmethod + def settings_type(cls) -> Type[schema.FeatureSettings]: + return SecurityProfileSettings + + @classmethod + def on_before_deployment(cls, *args: Any, **kwargs: Any) -> None: + environment = cast(Environment, kwargs.get("environment")) + security_profile = [kwargs.get("settings")] + for node in environment.nodes._list: + if security_profile: + settings = security_profile[0] + assert isinstance(settings, SecurityProfileSettings) + assert isinstance(settings.security_profile, SecurityProfileType) + node_context = get_node_context(node) + node_context.guest_vm_type = cls._security_profile_mapping[ + settings.security_profile + ] diff --git a/lisa/sut_orchestrator/libvirt/platform.py b/lisa/sut_orchestrator/libvirt/platform.py index 887176c359..c8d9f807be 100644 --- a/lisa/sut_orchestrator/libvirt/platform.py +++ b/lisa/sut_orchestrator/libvirt/platform.py @@ -20,7 +20,7 @@ import pycdlib # type: ignore import yaml -from lisa import schema, search_space +from lisa import feature, schema, search_space from lisa.environment import Environment from lisa.feature import Feature from lisa.node import Node, RemoteNode, local_node_connect @@ -42,7 +42,12 @@ Uname, Whoami, ) -from lisa.util import LisaException, constants, get_public_key_data +from lisa.util import ( + LisaException, + NotMeetRequirementException, + constants, + get_public_key_data, +) from lisa.util.logger import Logger, filter_ansi_escape, get_logger from . import libvirt_events_thread @@ -54,6 +59,7 @@ get_environment_context, get_node_context, ) +from .features import SecurityProfile, SecurityProfileSettings from .platform_interface import IBaseLibvirtPlatform from .schema import ( FIRMWARE_TYPE_BIOS, @@ -89,6 +95,7 @@ class BaseLibvirtPlatform(Platform, IBaseLibvirtPlatform): _supported_features: List[Type[Feature]] = [ SerialConsole, StartStop, + SecurityProfile, ] def __init__(self, runbook: schema.Platform) -> None: @@ -193,6 +200,36 @@ def _prepare_environment(self, environment: Environment, log: Logger) -> bool: self._configure_environment(environment, log) + if environment.runbook.nodes_requirement: + for node_space in environment.runbook.nodes_requirement: + new_settings = search_space.SetSpace[schema.FeatureSettings]( + is_allow_set=True + ) + if node_space.features: + for current_settings in node_space.features.items: + # reload to type specified settings + try: + settings_type = feature.get_feature_settings_type_by_name( + current_settings.type, + BaseLibvirtPlatform.supported_features(), + ) + except NotMeetRequirementException as identifier: + raise LisaException( + f"platform doesn't support all features. {identifier}" + ) + new_setting = schema.load_by_type( + settings_type, current_settings + ) + existing_setting = feature.get_feature_settings_by_name( + new_setting.type, new_settings, True + ) + if existing_setting: + new_settings.remove(existing_setting) + new_setting = existing_setting.intersect(new_setting) + + new_settings.add(new_setting) + node_space.features = new_settings + return self._configure_node_capabilities(environment, log) def _deploy_environment(self, environment: Environment, log: Logger) -> None: @@ -312,10 +349,12 @@ def _create_node_capabilities( node_capabilities.network_interface.max_nic_count = 1 node_capabilities.network_interface.nic_count = 1 node_capabilities.gpu_count = 0 + security_profile_setting = SecurityProfileSettings() node_capabilities.features = search_space.SetSpace[schema.FeatureSettings]( is_allow_set=True, items=[ schema.FeatureSettings.create(SerialConsole.name()), + security_profile_setting, ], ) @@ -564,6 +603,31 @@ def _create_nodes( log: Logger, ) -> None: self.host_node.shell.mkdir(Path(self.vm_disks_dir), exist_ok=True) + features_settings: Dict[str, schema.FeatureSettings] = {} + + # collect all the features to handle special deployment logic. If one + # node has this, it needs to run. + nodes_requirement = environment.runbook.nodes_requirement + if nodes_requirement: + for node_space in nodes_requirement: + if not node_space.features: + continue + for feature_setting in node_space.features: + if feature_setting.type not in features_settings: + features_settings[feature_setting.type] = feature_setting + + # change deployment for each feature. + for feature_type, setting in [ + (t, s) + for t in self.supported_features() + for s in features_settings.values() + if t.name() == s.type + ]: + feature_type.on_before_deployment( + environment=environment, + log=log, + settings=setting, + ) for node in environment.nodes.list(): node_context = get_node_context(node) diff --git a/lisa/util/constants.py b/lisa/util/constants.py index 42799c09eb..be80063d9a 100644 --- a/lisa/util/constants.py +++ b/lisa/util/constants.py @@ -116,6 +116,8 @@ SECURITY_PROFILE_CVM = "cvm" SECURITY_PROFILE_STATELESS = "stateless" +GUEST_VM_TYPE_STANDARD = "STANDARD" +GUEST_VM_TYPE_CVM = "CVM" PLATFORM = "platform" PLATFORM_READY = "ready" PLATFORM_BAREMETAL = "baremetal" From 298eeae5b116c1709723b447360a5173b5f3139f Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Tue, 24 Sep 2024 17:29:32 +0530 Subject: [PATCH 2/4] Add launch security to launch guest CVM --- lisa/sut_orchestrator/libvirt/ch_platform.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lisa/sut_orchestrator/libvirt/ch_platform.py b/lisa/sut_orchestrator/libvirt/ch_platform.py index de2c7e7a0d..8daa1fc5a0 100644 --- a/lisa/sut_orchestrator/libvirt/ch_platform.py +++ b/lisa/sut_orchestrator/libvirt/ch_platform.py @@ -3,6 +3,7 @@ import os import re +import secrets import xml.etree.ElementTree as ET # noqa: N817 from pathlib import Path from typing import List, Type @@ -113,9 +114,21 @@ def _create_node_domain_xml( os_type = ET.SubElement(os, "type") os_type.text = "hvm" - os_kernel = ET.SubElement(os, "kernel") - os_kernel.text = node_context.firmware_path + if node_context.guest_vm_type == "ConfidentialVM": + os_kernel.text = "/usr/share/cloud-hypervisor/cvm/linux.bin" + launch_sec = ET.SubElement(domain, "launchSecurity") + launch_sec.attrib["type"] = "sev" + cbitpos = ET.SubElement(launch_sec, "cbitpos") + cbitpos.text = "0" + reducedphysbits = ET.SubElement(launch_sec, "reducedPhysBits") + reducedphysbits.text = "0" + policy = ET.SubElement(launch_sec, "policy") + policy.text = "0" + host_data = ET.SubElement(launch_sec, "host_data") + host_data.text = secrets.token_hex(32) + else: + os_kernel.text = node_context.firmware_path devices = ET.SubElement(domain, "devices") if len(node_context.passthrough_devices) > 0: From d3e482998e3ba20bbfd3a991e1496414789c55d0 Mon Sep 17 00:00:00 2001 From: AASTHA RAWAT Date: Thu, 26 Sep 2024 11:32:23 +0530 Subject: [PATCH 3/4] Change cloud-hypervisor schema for kernel parameter in libvirt --- lisa/sut_orchestrator/libvirt/ch_platform.py | 39 +++++++++++++------- lisa/sut_orchestrator/libvirt/context.py | 13 +++++-- lisa/sut_orchestrator/libvirt/features.py | 24 ++++++------ lisa/sut_orchestrator/libvirt/schema.py | 12 +++++- lisa/util/constants.py | 2 - 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/lisa/sut_orchestrator/libvirt/ch_platform.py b/lisa/sut_orchestrator/libvirt/ch_platform.py index 8daa1fc5a0..cbd033a04b 100644 --- a/lisa/sut_orchestrator/libvirt/ch_platform.py +++ b/lisa/sut_orchestrator/libvirt/ch_platform.py @@ -12,7 +12,11 @@ from lisa.environment import Environment from lisa.feature import Feature from lisa.node import Node -from lisa.sut_orchestrator.libvirt.context import NodeContext, get_node_context +from lisa.sut_orchestrator.libvirt.context import ( + GuestVmType, + NodeContext, + get_node_context, +) from lisa.sut_orchestrator.libvirt.platform import BaseLibvirtPlatform from lisa.tools import QemuImg from lisa.util.logger import Logger, filter_ansi_escape @@ -58,13 +62,22 @@ def _configure_node( assert isinstance(node_runbook, CloudHypervisorNodeSchema) node_context = get_node_context(node) - if self.host_node.is_remote: - node_context.firmware_source_path = node_runbook.firmware - node_context.firmware_path = os.path.join( - self.vm_disks_dir, os.path.basename(node_runbook.firmware) - ) + if node_runbook.kernel: + if self.host_node.is_remote and not node_runbook.kernel.is_remote_path: + node_context.kernel_source_path = node_runbook.kernel.path + node_context.kernel_path = os.path.join( + self.vm_disks_dir, os.path.basename(node_runbook.kernel.path) + ) + else: + node_context.kernel_path = node_runbook.kernel.path else: - node_context.firmware_path = node_runbook.firmware + if self.host_node.is_remote: + node_context.kernel_source_path = node_runbook.firmware + node_context.kernel_path = os.path.join( + self.vm_disks_dir, os.path.basename(node_runbook.firmware) + ) + else: + node_context.kernel_path = node_runbook.firmware def _create_node( self, @@ -73,10 +86,10 @@ def _create_node( environment: Environment, log: Logger, ) -> None: - if node_context.firmware_source_path: + if node_context.kernel_source_path: self.host_node.shell.copy( - Path(node_context.firmware_source_path), - Path(node_context.firmware_path), + Path(node_context.kernel_source_path), + Path(node_context.kernel_path), ) super()._create_node( @@ -115,8 +128,8 @@ def _create_node_domain_xml( os_type = ET.SubElement(os, "type") os_type.text = "hvm" os_kernel = ET.SubElement(os, "kernel") - if node_context.guest_vm_type == "ConfidentialVM": - os_kernel.text = "/usr/share/cloud-hypervisor/cvm/linux.bin" + os_kernel.text = node_context.kernel_path + if node_context.guest_vm_type is GuestVmType.ConfidentialVM: launch_sec = ET.SubElement(domain, "launchSecurity") launch_sec.attrib["type"] = "sev" cbitpos = ET.SubElement(launch_sec, "cbitpos") @@ -127,8 +140,6 @@ def _create_node_domain_xml( policy.text = "0" host_data = ET.SubElement(launch_sec, "host_data") host_data.text = secrets.token_hex(32) - else: - os_kernel.text = node_context.firmware_path devices = ET.SubElement(domain, "devices") if len(node_context.passthrough_devices) > 0: diff --git a/lisa/sut_orchestrator/libvirt/context.py b/lisa/sut_orchestrator/libvirt/context.py index 0216652fad..77893a824d 100644 --- a/lisa/sut_orchestrator/libvirt/context.py +++ b/lisa/sut_orchestrator/libvirt/context.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from enum import Enum from typing import Any, Dict, List, Optional, Tuple import libvirt # type: ignore @@ -43,12 +44,18 @@ class DevicePassthroughContext: managed: str = "" +@dataclass +class GuestVmType(Enum): + Standard = "Standard" + ConfidentialVM = "ConfidentialVM" + + @dataclass class NodeContext: vm_name: str = "" - firmware_source_path: str = "" - firmware_path: str = "" - guest_vm_type: str = "" + kernel_source_path: str = "" + kernel_path: str = "" + guest_vm_type: GuestVmType = GuestVmType.Standard cloud_init_file_path: str = "" ignition_file_path: str = "" os_disk_source_file_path: Optional[str] = None diff --git a/lisa/sut_orchestrator/libvirt/features.py b/lisa/sut_orchestrator/libvirt/features.py index 7a963f9b8a..e5f281b48e 100644 --- a/lisa/sut_orchestrator/libvirt/features.py +++ b/lisa/sut_orchestrator/libvirt/features.py @@ -6,7 +6,7 @@ from lisa import features, schema, search_space from lisa.environment import Environment from lisa.features.security_profile import SecurityProfileType -from lisa.sut_orchestrator.libvirt.context import get_node_context +from lisa.sut_orchestrator.libvirt.context import GuestVmType, get_node_context @dataclass_json() @@ -31,8 +31,8 @@ def _call_requirement_method( class SecurityProfile(features.SecurityProfile): _security_profile_mapping = { - SecurityProfileType.Standard: "", - SecurityProfileType.CVM: "ConfidentialVM", + SecurityProfileType.Standard: GuestVmType.Standard, + SecurityProfileType.CVM: GuestVmType.ConfidentialVM, } def _initialize(self, *args: Any, **kwargs: Any) -> None: @@ -45,13 +45,13 @@ def settings_type(cls) -> Type[schema.FeatureSettings]: @classmethod def on_before_deployment(cls, *args: Any, **kwargs: Any) -> None: environment = cast(Environment, kwargs.get("environment")) - security_profile = [kwargs.get("settings")] + settings = kwargs.get("settings") + if not settings: + return for node in environment.nodes._list: - if security_profile: - settings = security_profile[0] - assert isinstance(settings, SecurityProfileSettings) - assert isinstance(settings.security_profile, SecurityProfileType) - node_context = get_node_context(node) - node_context.guest_vm_type = cls._security_profile_mapping[ - settings.security_profile - ] + assert isinstance(settings, SecurityProfileSettings) + assert isinstance(settings.security_profile, SecurityProfileType) + node_context = get_node_context(node) + node_context.guest_vm_type = cls._security_profile_mapping[ + settings.security_profile + ] diff --git a/lisa/sut_orchestrator/libvirt/schema.py b/lisa/sut_orchestrator/libvirt/schema.py index 2c85bbebd6..eb086e967b 100644 --- a/lisa/sut_orchestrator/libvirt/schema.py +++ b/lisa/sut_orchestrator/libvirt/schema.py @@ -133,10 +133,20 @@ def __post_init__(self) -> None: self.disk_img_format = DiskImageFormat.QCOW2.value +@dataclass_json() +@dataclass +class KernelSchema: + path: str = "" + is_remote_path: bool = False + + @dataclass_json() @dataclass class CloudHypervisorNodeSchema(BaseLibvirtNodeSchema): + # DEPRECATED: use the 'kernel' field instead. # Local path to the cloud-hypervisor firmware. - # Can be obatained from: + # Can be obtained from: # https://github.com/cloud-hypervisor/rust-hypervisor-firmware firmware: str = "" + # Local or remote path to the cloud-hypervisor kernel. + kernel: Optional[KernelSchema] = None diff --git a/lisa/util/constants.py b/lisa/util/constants.py index be80063d9a..42799c09eb 100644 --- a/lisa/util/constants.py +++ b/lisa/util/constants.py @@ -116,8 +116,6 @@ SECURITY_PROFILE_CVM = "cvm" SECURITY_PROFILE_STATELESS = "stateless" -GUEST_VM_TYPE_STANDARD = "STANDARD" -GUEST_VM_TYPE_CVM = "CVM" PLATFORM = "platform" PLATFORM_READY = "ready" PLATFORM_BAREMETAL = "baremetal" From 5b3cfc13482bb1267aa50b49289dba69de89470b Mon Sep 17 00:00:00 2001 From: BumpDeps <> Date: Mon, 21 Oct 2024 02:16:20 +0000 Subject: [PATCH 4/4] BumpDeps: isort --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7ccbfedca8..893a079292 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ flake8 = [ ] isort = [ - "isort ~= 5.12.0", + "isort ~= 5.13.2", ] legacy = [