Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ol qemu confidential controller #620

Merged
merged 10 commits into from
Jun 14, 2024
51 changes: 51 additions & 0 deletions examples/confidential_instance_message_from_aleph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"chain": "ETH",
"item_hash": "fake-hash-fake-hash-fake-hash-fake-hash-fake-hash-fake-hash-hash",
"sender": "0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba",
"type": "INSTANCE",
"channel": "Fun-dApps",
"confirmed": true,
"content": {
"address": "0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba",
"allow_amend": false,
"variables": {
"VM_CUSTOM_NUMBER": "32"
},
"environment": {
"reproducible": true,
"internet": true,
"aleph_api": true,
"shared_cache": true
},
"resources": {
"vcpus": 1,
"memory": 512,
"seconds": 30
},
"rootfs": {
"parent": {
"ref": "549ec451d9b099cad112d4aaa2c00ac40fb6729a92ff252ff22eef0b5c3cb613",
"use_latest": true
},
"persistence": "host",
"size_mib": 5000
},
"authorized_keys": [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDj95BHGUx0/z2G/tTrEi8o49i70xvjcEUdSs3j4A33jE7pAphrfRVbuFMgFubcm8n9r5ftd/H8SjjTL4hY9YvWV5ZuMf92GUga3n4wgevvPlBszYZCy/idxFl0vtHYC1CcK9v4tVb9onhDt8FOJkf2m6PmDyvC+6tl6LwoerXTeeiKr5VnTB4KOBkammtFmix3d1X1SZd/cxdwZIHcQ7BNsqBm2w/YzVba6Z4ZnFUelBkQtMQqNs2aV51O1pFFqtZp2mM71D5d8vn9pOtqJ5QmY5IW6NypcyqKJZg5o6QguK5rdXLkc7AWro27BiaHIENl3w0wazp9EDO9zPAGJ6lz olivier@lanius"
],

"time": 1619017773.8950517
},
"item_content": "{\"address\":\"0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba\",\"allow_amend\":false,\"variables\":{\"VM_CUSTOM_NUMBER\":\"32\"},\"environment\":{\"reproducible\":true,\"internet\":true,\"aleph_api\":true,\"shared_cache\":true},\"resources\":{\"vcpus\":1,\"memory\":128,\"seconds\":30},\"rootfs\":{\"parent\":{\"ref\":\"549ec451d9b099cad112d4aaa2c00ac40fb6729a92ff252ff22eef0b5c3cb613\",\"use_latest\":true},\"persistence\":\"host\",\"size_mib\":20000},\"cloud_config\":{\"password\":\"password\",\"chpasswd\":{\"expire\":\"False\"}},\"volumes\":[{\"mount\":\"/opt/venv\",\"ref\":\"5f31b0706f59404fad3d0bff97ef89ddf24da4761608ea0646329362c662ba51\",\"use_latest\":false},{\"comment\":\"Working data persisted on the VM supervisor, not available on other nodes\",\"mount\":\"/var/lib/example\",\"name\":\"data\",\"persistence\":\"host\",\"size_mib\":5}],\"replaces\":\"0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba\",\"time\":1619017773.8950517}",
"item_type": "inline",
"signature": "0x372da8230552b8c3e65c05b31a0ff3a24666d66c575f8e11019f62579bf48c2b7fe2f0bbe907a2a5bf8050989cdaf8a59ff8a1cbcafcdef0656c54279b4aa0c71b",
"size": 749,
"time": 1619017773.8950577,
"confirmations": [
{
"chain": "ETH",
"height": 12284734,
"hash": "0x67f2f3cde5e94e70615c92629c70d22dc959a118f46e9411b29659c2fce87cdc"
}
]
}
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
]
dependencies = [
"pydantic[dotenv]~=1.10.13",
"aiohttp==3.8.6",
"aiohttp==3.9.5",
"aiodns==3.1.0",
"setproctitle==1.3.3",
"pyyaml==6.0.1",
Expand All @@ -51,6 +51,7 @@ dependencies = [
"aiohttp_cors~=0.7.0",
"pyroute2==0.7.12",
"jwcrypto==1.5.6",
"python-cpuid==0.1.0"
]

[project.urls]
Expand Down
8 changes: 7 additions & 1 deletion src/aleph/vm/controllers/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

from aleph.vm.hypervisors.firecracker.microvm import MicroVM
from aleph.vm.hypervisors.qemu.qemuvm import QemuVM
from aleph.vm.hypervisors.qemu_confidential.qemuvm import QemuConfidentialVM
from aleph.vm.network.hostnetwork import Network, make_ipv6_allocator

from .configuration import (
Configuration,
HypervisorType,
QemuConfidentialVMConfiguration,
QemuVMConfiguration,
VMConfiguration,
)
Expand Down Expand Up @@ -70,6 +72,10 @@

execution.prepare_start()
process = await execution.start(config.vm_configuration.config_file_path)
elif isinstance(config.vm_configuration, QemuConfidentialVMConfiguration): # FIXME
assert isinstance(config.vm_configuration, QemuConfidentialVMConfiguration)
execution = QemuConfidentialVM(config.vm_configuration)
process = await execution.start()

Check warning on line 78 in src/aleph/vm/controllers/__main__.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/__main__.py#L76-L78

Added lines #L76 - L78 were not covered by tests
else:
assert isinstance(config.vm_configuration, QemuVMConfiguration)
execution = QemuVM(config.vm_configuration)
Expand All @@ -87,7 +93,7 @@
execution.start_printing_logs()

await process.wait()
logger.info(f"Process terminated with {process.returncode}")
logger.warning(f"Process terminated with {process.returncode}")

Check warning on line 96 in src/aleph/vm/controllers/__main__.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/__main__.py#L96

Added line #L96 was not covered by tests


async def run_persistent_vm(config: Configuration):
Expand Down
16 changes: 15 additions & 1 deletion src/aleph/vm/controllers/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ class QemuVMConfiguration(BaseModel):
interface_name: Optional[str]


class QemuConfidentialVMConfiguration(BaseModel):
qemu_bin_path: str
cloud_init_drive_path: Optional[str]
image_path: str
monitor_socket_path: Path
qmp_socket_path: Path
vcpu_count: int
mem_size_mb: int
interface_name: Optional[str]
nesitor marked this conversation as resolved.
Show resolved Hide resolved
ovmf_path: Path
sev_session_file: Path
sev_dh_cert_file: Path


class HypervisorType(str, Enum):
qemu = "qemu"
firecracker = "firecracker"
Expand All @@ -37,7 +51,7 @@ class HypervisorType(str, Enum):
class Configuration(BaseModel):
vm_id: int
settings: Settings
vm_configuration: Union[QemuVMConfiguration, VMConfiguration]
vm_configuration: Union[QemuConfidentialVMConfiguration, QemuVMConfiguration, VMConfiguration]
hypervisor: HypervisorType = HypervisorType.firecracker


Expand Down
75 changes: 75 additions & 0 deletions src/aleph/vm/controllers/qemu/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import qmp
from pydantic import BaseModel


class VmSevInfo(BaseModel):
enabled: bool
api_major: int
api_minor: int
build_id: int
policy: int
state: str
handle: int


class QemuVmClient:
def __init__(self, vm):
self.vm = vm

Check warning on line 17 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L17

Added line #L17 was not covered by tests
if not (vm.qmp_socket_path and vm.qmp_socket_path.exists()):
raise Exception
client = qmp.QEMUMonitorProtocol(str(vm.qmp_socket_path))
client.connect()

Check warning on line 21 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L19-L21

Added lines #L19 - L21 were not covered by tests

# qmp_client = qmp.QEMUMonitorProtocol(address=("localhost", vm.qmp_port))
self.qmp_client = client

Check warning on line 24 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L24

Added line #L24 was not covered by tests

def __enter__(self):
return self

Check warning on line 27 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L27

Added line #L27 was not covered by tests

def __exit__(self, exc_type, exc_val, exc_tb):
self.close()

Check warning on line 30 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L30

Added line #L30 was not covered by tests

def close(self) -> None:
self.qmp_client.close()

Check warning on line 33 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L33

Added line #L33 was not covered by tests

def query_sev_info(self) -> VmSevInfo:
caps = self.qmp_client.command("query-sev")
return VmSevInfo(

Check warning on line 37 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L36-L37

Added lines #L36 - L37 were not covered by tests
enabled=caps["enabled"],
api_major=caps["api-major"],
api_minor=caps["api-minor"],
handle=caps["handle"],
state=caps["state"],
build_id=caps["build-id"],
policy=caps["policy"],
)

def query_launch_measure(self) -> str:
measure = self.qmp_client.command("query-sev-launch-measure")
return measure["data"]

Check warning on line 49 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L48-L49

Added lines #L48 - L49 were not covered by tests

def inject_secret(self, packet_header: str, secret: str) -> None:
"""
Injects the secret in the SEV secret area.

:param packet_header: The packet header, as a base64 string.
:param secret: The encoded secret, as a base64 string.
"""

self.qmp_client.command(

Check warning on line 59 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L59

Added line #L59 was not covered by tests
"sev-inject-launch-secret",
**{"packet-header": packet_header, "secret": secret},
)

def continue_execution(self) -> None:
"""
Resumes the execution of the VM.
"""
self.qmp_client.command("cont")

Check warning on line 68 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L68

Added line #L68 was not covered by tests

def query_status(self) -> None:
"""
Get running status.
"""
# {'status': 'prelaunch', 'singlestep': False, 'running': False}
return self.qmp_client.command("query-status")

Check warning on line 75 in src/aleph/vm/controllers/qemu/client.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu/client.py#L75

Added line #L75 was not covered by tests
12 changes: 7 additions & 5 deletions src/aleph/vm/controllers/qemu/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ class AlephQemuInstance(Generic[ConfigurationType], CloudInitMixin, AlephVmContr
is_instance: bool
qemu_process: Optional[Process]
support_snapshot = False
qmp_socket_path = None
persistent = True
_queue_cancellers: dict[asyncio.Queue, Callable] = {}
controller_configuration: Configuration
Expand Down Expand Up @@ -219,7 +218,7 @@ async def configure(self):

logger.debug(f"Making Qemu configuration: {self} ")
monitor_socket_path = settings.EXECUTION_ROOT / (str(self.vm_id) + "-monitor.socket")
self.qmp_socket_path = qmp_socket_path = settings.EXECUTION_ROOT / (str(self.vm_id) + "-qmp.socket")

cloud_init_drive = await self._create_cloud_init_drive()

image_path = str(self.resources.rootfs_path)
Expand All @@ -237,7 +236,7 @@ async def configure(self):
cloud_init_drive_path=cloud_init_drive_path,
image_path=image_path,
monitor_socket_path=monitor_socket_path,
qmp_socket_path=qmp_socket_path,
qmp_socket_path=self.qmp_socket_path,
vcpu_count=vcpu_count,
mem_size_mb=mem_size_mb,
interface_name=interface_name,
Expand All @@ -246,7 +245,7 @@ async def configure(self):
configuration = Configuration(
vm_id=self.vm_id, settings=settings, vm_configuration=vm_configuration, hypervisor=HypervisorType.qemu
)

logger.debug(configuration)
save_controller_configuration(self.vm_hash, configuration)

def save_controller_configuration(self):
Expand All @@ -260,6 +259,10 @@ def save_controller_configuration(self):
def _journal_stdout_name(self) -> str:
return f"vm-{self.vm_hash}-stdout"

@property
def qmp_socket_path(self) -> Path:
return settings.EXECUTION_ROOT / f"{self.vm_id}-qmp.socket"

@property
def _journal_stderr_name(self) -> str:
return f"vm-{self.vm_hash}-stderr"
Expand All @@ -276,7 +279,6 @@ async def wait_for_init(self) -> None:
if not ip:
msg = "Host IP not available"
raise ValueError(msg)

ip = ip.split("/", 1)[0]

attempts = 30
Expand Down
Empty file.
118 changes: 118 additions & 0 deletions src/aleph/vm/controllers/qemu_confidential/instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import asyncio
import logging
import shutil
from asyncio.subprocess import Process
from typing import Callable, Optional

from aleph_message.models import ItemHash
from aleph_message.models.execution.environment import MachineResources

from aleph.vm.conf import settings
from aleph.vm.controllers.configuration import (
Configuration,
HypervisorType,
QemuConfidentialVMConfiguration,
save_controller_configuration,
)
from aleph.vm.controllers.qemu import AlephQemuInstance
from aleph.vm.controllers.qemu.instance import (
AlephQemuResources,
ConfigurationType,
logger,
)
from aleph.vm.network.interfaces import TapInterface

logger = logging.getLogger(__name__)


class AlephQemuConfidentialResources(AlephQemuResources):
# TODO: Implement download of the custom OVMF bootloader to use if specified, if not only use the default one.
pass
olethanh marked this conversation as resolved.
Show resolved Hide resolved


class AlephQemuConfidentialInstance(AlephQemuInstance):
vm_id: int
olethanh marked this conversation as resolved.
Show resolved Hide resolved
vm_hash: ItemHash
resources: AlephQemuResources
enable_console: bool
enable_networking: bool
hardware_resources: MachineResources
tap_interface: Optional[TapInterface] = None
vm_configuration: Optional[ConfigurationType]
is_instance: bool
qemu_process: Optional[Process]
support_snapshot = False
persistent = True
_queue_cancellers: dict[asyncio.Queue, Callable] = {}
controller_configuration: Configuration

def __repr__(self):
return f"<AlephQemuInstance {self.vm_id}>"

Check warning on line 50 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L50

Added line #L50 was not covered by tests

def __str__(self):
return f"vm-{self.vm_id}"

Check warning on line 53 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L53

Added line #L53 was not covered by tests

def __init__(
self,
vm_id: int,
vm_hash: ItemHash,
resources: AlephQemuResources,
enable_networking: bool = False,
enable_console: Optional[bool] = None,
hardware_resources: MachineResources = MachineResources(),
tap_interface: Optional[TapInterface] = None,
):
super().__init__(

Check warning on line 65 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L65

Added line #L65 was not covered by tests
vm_id, vm_hash, resources, enable_networking, enable_console, hardware_resources, tap_interface
)

async def setup(self):
pass

Check warning on line 70 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L70

Added line #L70 was not covered by tests

async def configure(self):
"""Configure the VM by saving controller service configuration"""

logger.debug(f"Making Qemu configuration: {self} ")
monitor_socket_path = settings.EXECUTION_ROOT / (str(self.vm_id) + "-monitor.socket")

Check warning on line 76 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L75-L76

Added lines #L75 - L76 were not covered by tests

cloud_init_drive = await self._create_cloud_init_drive()

Check warning on line 78 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L78

Added line #L78 was not covered by tests

image_path = str(self.resources.rootfs_path)
vcpu_count = self.hardware_resources.vcpus
mem_size_mib = self.hardware_resources.memory
mem_size_mb = str(int(mem_size_mib / 1024 / 1024 * 1000 * 1000))

Check warning on line 83 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L80-L83

Added lines #L80 - L83 were not covered by tests
nesitor marked this conversation as resolved.
Show resolved Hide resolved

vm_session_path = settings.CONFIDENTIAL_SESSION_DIRECTORY / self.vm_hash
session_file_path = vm_session_path / "vm_session.b64"
godh_file_path = vm_session_path / "vm_godh.b64"

Check warning on line 87 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L85-L87

Added lines #L85 - L87 were not covered by tests

qemu_bin_path = shutil.which("qemu-system-x86_64")
interface_name = None

Check warning on line 90 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L89-L90

Added lines #L89 - L90 were not covered by tests
if self.tap_interface:
interface_name = self.tap_interface.device_name
cloud_init_drive_path = str(cloud_init_drive.path_on_host) if cloud_init_drive else None
vm_configuration = QemuConfidentialVMConfiguration(

Check warning on line 94 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L92-L94

Added lines #L92 - L94 were not covered by tests
qemu_bin_path=qemu_bin_path,
cloud_init_drive_path=cloud_init_drive_path,
image_path=image_path,
monitor_socket_path=monitor_socket_path,
qmp_socket_path=self.qmp_socket_path,
vcpu_count=vcpu_count,
mem_size_mb=mem_size_mb,
interface_name=interface_name,
ovmf_path="/home/olivier/custom-OVMF.fd",
sev_session_file=session_file_path,
sev_dh_cert_file=godh_file_path,
)

configuration = Configuration(

Check warning on line 108 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L108

Added line #L108 was not covered by tests
vm_id=self.vm_id, settings=settings, vm_configuration=vm_configuration, hypervisor=HypervisorType.qemu
)
logger.debug(configuration)

Check warning on line 111 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L111

Added line #L111 was not covered by tests

save_controller_configuration(self.vm_hash, configuration)

Check warning on line 113 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L113

Added line #L113 was not covered by tests

async def wait_for_init(self) -> None:
"""Wait for the init process of the instance to be ready."""
# FIXME: Cannot ping since network is not set up yet.
return

Check warning on line 118 in src/aleph/vm/controllers/qemu_confidential/instance.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/controllers/qemu_confidential/instance.py#L118

Added line #L118 was not covered by tests
nesitor marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion src/aleph/vm/hypervisors/qemu/qemuvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
return
for queue in self.log_queues:
await queue.put(("stdout", line))
print(self, line.decode().strip())
print(self, line)

Check warning on line 135 in src/aleph/vm/hypervisors/qemu/qemuvm.py

View check run for this annotation

Codecov / codecov/patch

src/aleph/vm/hypervisors/qemu/qemuvm.py#L135

Added line #L135 was not covered by tests

def _get_qmpclient(self) -> Optional[qmp.QEMUMonitorProtocol]:
if not (self.qmp_socket_path and self.qmp_socket_path.exists()):
Expand Down
Empty file.
Loading