From 068efa2b0d03f4b621febadb3de2d088ee7dd51c Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Tue, 2 Jul 2024 14:50:32 +0200 Subject: [PATCH 1/3] Problem: As a user we cannot create a confidential VM using the SDK. Solution: Implement new confidential VM fields. --- src/aleph/sdk/client/authenticated_http.py | 15 ++++++++++ tests/unit/test_asynchronous.py | 34 +++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py index 6d44b526..3c6bbc08 100644 --- a/src/aleph/sdk/client/authenticated_http.py +++ b/src/aleph/sdk/client/authenticated_http.py @@ -29,9 +29,11 @@ from aleph_message.models.execution.base import Encoding, Payment, PaymentType from aleph_message.models.execution.environment import ( FunctionEnvironment, + HostRequirements, HypervisorType, InstanceEnvironment, MachineResources, + TrustedExecutionEnvironment, ) from aleph_message.models.execution.instance import RootfsVolume from aleph_message.models.execution.program import CodeContent, FunctionRuntime @@ -522,10 +524,13 @@ async def create_instance( internet: bool = True, aleph_api: bool = True, hypervisor: Optional[HypervisorType] = None, + confidential_firmware: Optional[ItemHash] = None, + confidential_policy: Optional[int] = None, volumes: Optional[List[Mapping]] = None, volume_persistence: str = "host", ssh_keys: Optional[List[str]] = None, metadata: Optional[Mapping[str, Any]] = None, + requirements: Optional[HostRequirements] = None, ) -> Tuple[InstanceMessage, MessageStatus]: address = address or settings.ADDRESS_TO_USE or self.account.get_address() @@ -536,6 +541,14 @@ async def create_instance( payment = payment or Payment(chain=Chain.ETH, type=PaymentType.hold) + if confidential_firmware or confidential_policy: + confidential_options = TrustedExecutionEnvironment( + firmware=confidential_firmware, + policy=confidential_policy, + ) + else: + confidential_options = None + # Default to the QEMU hypervisor for instances. selected_hypervisor: HypervisorType = hypervisor or HypervisorType.qemu @@ -546,6 +559,7 @@ async def create_instance( internet=internet, aleph_api=aleph_api, hypervisor=selected_hypervisor, + trusted_execution=confidential_options, ), variables=environment_variables, resources=MachineResources( @@ -563,6 +577,7 @@ async def create_instance( use_latest=True, ), volumes=[parse_volume(volume) for volume in volumes], + requirements=requirements, time=time.time(), authorized_keys=ssh_keys, metadata=metadata, diff --git a/tests/unit/test_asynchronous.py b/tests/unit/test_asynchronous.py index 0f909408..0839c9de 100644 --- a/tests/unit/test_asynchronous.py +++ b/tests/unit/test_asynchronous.py @@ -14,7 +14,12 @@ ProgramMessage, StoreMessage, ) -from aleph_message.models.execution.environment import HypervisorType, MachineResources +from aleph_message.models.execution.environment import ( + HostRequirements, + HypervisorType, + MachineResources, + NodeRequirements, +) from aleph_message.status import MessageStatus from aleph.sdk.exceptions import InsufficientFundsError @@ -163,6 +168,33 @@ async def test_create_instance_no_hypervisor(mock_session_with_post_success): assert isinstance(instance_message, InstanceMessage) +@pytest.mark.asyncio +async def test_create_confidential_instance(mock_session_with_post_success): + async with mock_session_with_post_success as session: + confidential_instance_message, message_status = await session.create_instance( + rootfs="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe", + rootfs_size=1, + channel="TEST", + metadata={"tags": ["test"]}, + payment=Payment( + chain=Chain.AVAX, + receiver="0x4145f182EF2F06b45E50468519C1B92C60FBd4A0", + type=PaymentType.superfluid, + ), + hypervisor=HypervisorType.qemu, + confidential_firmware="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe", + confidential_policy=0b1, + requirements=HostRequirements( + node=NodeRequirements( + node_hash="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe", + ) + ), + ) + + assert mock_session_with_post_success.http_session.post.assert_called_once + assert isinstance(confidential_instance_message, InstanceMessage) + + @pytest.mark.asyncio async def test_forget(mock_session_with_post_success): async with mock_session_with_post_success as session: From d06b41a34c5fdeb2ce734c9a1b91daa48087f6eb Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Tue, 2 Jul 2024 14:56:44 +0200 Subject: [PATCH 2/3] Fix: Solve code quality issues. --- src/aleph/sdk/client/abstract.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/abstract.py b/src/aleph/sdk/client/abstract.py index 9fce5469..d0226423 100644 --- a/src/aleph/sdk/client/abstract.py +++ b/src/aleph/sdk/client/abstract.py @@ -27,7 +27,7 @@ PostMessage, parse_message, ) -from aleph_message.models.execution.environment import HypervisorType +from aleph_message.models.execution.environment import HostRequirements, HypervisorType from aleph_message.models.execution.program import Encoding from aleph_message.status import MessageStatus @@ -395,10 +395,13 @@ async def create_instance( internet: bool = True, aleph_api: bool = True, hypervisor: Optional[HypervisorType] = None, + confidential_firmware: Optional[ItemHash] = None, + confidential_policy: Optional[int] = None, volumes: Optional[List[Mapping]] = None, volume_persistence: str = "host", ssh_keys: Optional[List[str]] = None, metadata: Optional[Mapping[str, Any]] = None, + requirements: Optional[HostRequirements] = None, ) -> Tuple[AlephMessage, MessageStatus]: """ Post a (create) INSTANCE message. @@ -417,11 +420,15 @@ async def create_instance( :param allow_amend: Whether the deployed VM image may be changed (Default: False) :param internet: Whether the VM should have internet connectivity. (Default: True) :param aleph_api: Whether the VM needs access to Aleph messages API (Default: True) + :param hypervisor: Whether the VM should use as Hypervisor, like QEmu or Firecracker (Default: Qemu) + :param confidential_firmware: Whether the VM firmware to use for Confidential computing (Default: None) + :param confidential_policy: Whether the VM firmware policy to use for Confidential computing (Default: None) :param encoding: Encoding to use (Default: Encoding.zip) :param volumes: Volumes to mount :param volume_persistence: Where volumes are persisted, can be "host" or "store", meaning distributed across Aleph.im (Default: "host") :param ssh_keys: SSH keys to authorize access to the VM :param metadata: Metadata to attach to the message + :param requirements: CRN Requirements needed for the VM execution """ raise NotImplementedError( "Did you mean to import `AuthenticatedAlephHttpClient`?" From 7ffeccc60f63c168f780c3aaf14673454f05cf50 Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Fri, 5 Jul 2024 15:16:42 +0200 Subject: [PATCH 3/3] Fix: Changed `create_instance` method signature to be similar to aleph-message schema. --- src/aleph/sdk/client/abstract.py | 12 +++++++----- src/aleph/sdk/client/authenticated_http.py | 13 ++----------- tests/unit/test_asynchronous.py | 7 +++++-- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/aleph/sdk/client/abstract.py b/src/aleph/sdk/client/abstract.py index d0226423..23c30e81 100644 --- a/src/aleph/sdk/client/abstract.py +++ b/src/aleph/sdk/client/abstract.py @@ -27,7 +27,11 @@ PostMessage, parse_message, ) -from aleph_message.models.execution.environment import HostRequirements, HypervisorType +from aleph_message.models.execution.environment import ( + HostRequirements, + HypervisorType, + TrustedExecutionEnvironment, +) from aleph_message.models.execution.program import Encoding from aleph_message.status import MessageStatus @@ -395,8 +399,7 @@ async def create_instance( internet: bool = True, aleph_api: bool = True, hypervisor: Optional[HypervisorType] = None, - confidential_firmware: Optional[ItemHash] = None, - confidential_policy: Optional[int] = None, + trusted_execution: Optional[TrustedExecutionEnvironment] = None, volumes: Optional[List[Mapping]] = None, volume_persistence: str = "host", ssh_keys: Optional[List[str]] = None, @@ -421,8 +424,7 @@ async def create_instance( :param internet: Whether the VM should have internet connectivity. (Default: True) :param aleph_api: Whether the VM needs access to Aleph messages API (Default: True) :param hypervisor: Whether the VM should use as Hypervisor, like QEmu or Firecracker (Default: Qemu) - :param confidential_firmware: Whether the VM firmware to use for Confidential computing (Default: None) - :param confidential_policy: Whether the VM firmware policy to use for Confidential computing (Default: None) + :param trusted_execution: Whether the VM configuration (firmware and policy) to use for Confidential computing (Default: None) :param encoding: Encoding to use (Default: Encoding.zip) :param volumes: Volumes to mount :param volume_persistence: Where volumes are persisted, can be "host" or "store", meaning distributed across Aleph.im (Default: "host") diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py index 3c6bbc08..cb2f3e01 100644 --- a/src/aleph/sdk/client/authenticated_http.py +++ b/src/aleph/sdk/client/authenticated_http.py @@ -524,8 +524,7 @@ async def create_instance( internet: bool = True, aleph_api: bool = True, hypervisor: Optional[HypervisorType] = None, - confidential_firmware: Optional[ItemHash] = None, - confidential_policy: Optional[int] = None, + trusted_execution: Optional[TrustedExecutionEnvironment] = None, volumes: Optional[List[Mapping]] = None, volume_persistence: str = "host", ssh_keys: Optional[List[str]] = None, @@ -541,14 +540,6 @@ async def create_instance( payment = payment or Payment(chain=Chain.ETH, type=PaymentType.hold) - if confidential_firmware or confidential_policy: - confidential_options = TrustedExecutionEnvironment( - firmware=confidential_firmware, - policy=confidential_policy, - ) - else: - confidential_options = None - # Default to the QEMU hypervisor for instances. selected_hypervisor: HypervisorType = hypervisor or HypervisorType.qemu @@ -559,7 +550,7 @@ async def create_instance( internet=internet, aleph_api=aleph_api, hypervisor=selected_hypervisor, - trusted_execution=confidential_options, + trusted_execution=trusted_execution, ), variables=environment_variables, resources=MachineResources( diff --git a/tests/unit/test_asynchronous.py b/tests/unit/test_asynchronous.py index 0839c9de..b044e170 100644 --- a/tests/unit/test_asynchronous.py +++ b/tests/unit/test_asynchronous.py @@ -19,6 +19,7 @@ HypervisorType, MachineResources, NodeRequirements, + TrustedExecutionEnvironment, ) from aleph_message.status import MessageStatus @@ -182,8 +183,10 @@ async def test_create_confidential_instance(mock_session_with_post_success): type=PaymentType.superfluid, ), hypervisor=HypervisorType.qemu, - confidential_firmware="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe", - confidential_policy=0b1, + trusted_execution=TrustedExecutionEnvironment( + firmware="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe", + policy=0b1, + ), requirements=HostRequirements( node=NodeRequirements( node_hash="cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe",