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

Implement Confidential message type #99

Merged
merged 10 commits into from
Jun 18, 2024
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

- name: Test with ruff in the Docker image
run: |
docker run aleph-message:${GITHUB_REF##*/} ruff aleph_message
docker run aleph-message:${GITHUB_REF##*/} ruff check aleph_message

- name: Pytest in the Docker image
run: |
Expand Down
6 changes: 5 additions & 1 deletion aleph_message/models/execution/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
from .instance import InstanceContent
from .program import ProgramContent

__all__ = ["BaseExecutableContent", "InstanceContent", "ProgramContent"]
__all__ = [
"BaseExecutableContent",
"InstanceContent",
"ProgramContent",
]
11 changes: 8 additions & 3 deletions aleph_message/models/execution/abstract.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from __future__ import annotations

from abc import ABC
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Union

from pydantic import Field

from ..abstract import BaseContent, HashableModel
from .base import Payment
from .environment import FunctionEnvironment, HostRequirements, MachineResources
from .environment import (
FunctionEnvironment,
HostRequirements,
InstanceEnvironment,
MachineResources,
)
from .volume import MachineVolume


Expand All @@ -22,7 +27,7 @@ class BaseExecutableContent(HashableModel, BaseContent, ABC):
variables: Optional[Dict[str, str]] = Field(
default=None, description="Environment variables available in the VM"
)
environment: FunctionEnvironment = Field(
environment: Union[FunctionEnvironment, InstanceEnvironment] = Field(
description="Properties of the execution environment"
)
resources: MachineResources = Field(description="System resources required")
Expand Down
63 changes: 61 additions & 2 deletions aleph_message/models/execution/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from enum import Enum
from typing import List, Literal, Optional, Union

from pydantic import Extra, Field
from pydantic import Extra, Field, validator

from ...utils import Mebibytes
from ..abstract import HashableModel
from ..item_hash import ItemHash


class Subscription(HashableModel):
Expand Down Expand Up @@ -92,14 +93,72 @@ class FunctionEnvironment(HashableModel):
internet: bool = False
aleph_api: bool = False
shared_cache: bool = False
hypervisor: Optional[HypervisorType]
hoh marked this conversation as resolved.
Show resolved Hide resolved


class AMDSEVPolicy(int, Enum):
"""AMD Guest Policy for SEV-ES and SEV.

The firmware maintains a guest policy provided by the guest owner. This policy is enforced by the
firmware and restricts what configuration and operational commands can be performed on this
guest by the hypervisor. The policy also requires a minimum firmware level.

The policy comprises a set of flags that can be combined with bitwise OR.

See https://github.com/virtee/sev/blob/fbfed998930a0d1e6126462b371890b9f8d77148/src/launch/sev.rs#L245 for reference.
"""

NO_DBG = 0b1 # Debugging of the guest is disallowed
NO_KS = 0b10 # Sharing keys with other guests is disallowed
SEV_ES = 0b100 # SEV-ES is required
NO_SEND = 0b1000 # Sending the guest to another platform is disallowed
DOMAIN = 0b10000 # The guest must not be transmitted to another platform that is not in the domain
SEV = 0b100000 # The guest must not be transmitted to another platform that is not SEV capable


class TrustedExecutionEnvironment(HashableModel):
"""Trusted Execution Environment properties."""

firmware: Optional[ItemHash] = Field(
default=None, description="Confidential OVMF firmware to use"
)
policy: int = Field(
default=AMDSEVPolicy.NO_DBG,
description="Policy of the TEE. Default value is 0x01 for SEV without debugging.",
)

class Config:
extra = Extra.allow


class InstanceEnvironment(HashableModel):
internet: bool = False
aleph_api: bool = False
hypervisor: Optional[HypervisorType] = Field(
default=None, description="Hypervisor application to use. Default value is QEmu"
hoh marked this conversation as resolved.
Show resolved Hide resolved
)
trusted_execution: Optional[TrustedExecutionEnvironment] = Field(
default=None,
description="Trusted Execution Environment properties. Defaults to no TEE.",
)
# The following fields are kept for retro-compatibility.
reproducible: bool = False
shared_cache: bool = False

@validator("trusted_execution", pre=True)
def check_hypervisor(cls, v, values):
if v and values.get("hypervisor") != HypervisorType.qemu:
raise ValueError("Trusted Execution Environment is only supported for QEmu")
return v


class NodeRequirements(HashableModel):
owner: Optional[str] = Field(default=None, description="Address of the node owner")
address_regex: Optional[str] = Field(
default=None, description="Node address must match this regular expression"
)
node_hash: Optional[ItemHash] = Field(
default=None, description="Hash of the compute resource node that must be used"
)

class Config:
extra = Extra.forbid
Expand Down
4 changes: 4 additions & 0 deletions aleph_message/models/execution/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from aleph_message.models.abstract import HashableModel

from .abstract import BaseExecutableContent
from .environment import InstanceEnvironment
from .volume import ParentVolume, PersistentVolumeSizeMib, VolumePersistence


Expand All @@ -25,6 +26,9 @@ class RootfsVolume(HashableModel):
class InstanceContent(BaseExecutableContent):
"""Message content for scheduling a VM instance on the network."""

environment: InstanceEnvironment = Field(
description="Properties of the instance execution environment"
)
rootfs: RootfsVolume = Field(
description="Root filesystem of the system, will be booted by the kernel"
)
102 changes: 102 additions & 0 deletions aleph_message/tests/messages/instance_confidential_machine.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"_id": {
"$oid": "6080402d7f44efefd611dc1e"
},
"chain": "ETH",
"sender": "0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba",
"type": "INSTANCE",
"channel": "Fun-dApps",
"confirmed": true,
"content": {
"address": "0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba",
"allow_amend": false,
"variables": {
"VM_CUSTOM_VARIABLE": "SOMETHING",
"VM_CUSTOM_VARIABLE_2": "32"
},
"environment": {
"reproducible": true,
"internet": false,
"aleph_api": false,
"shared_cache": false,
"hypervisor": "qemu",
"trusted_execution": {
"policy": 1,
"firmware": "e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317"
}
},
"resources": {
"vcpus": 1,
"memory": 128,
"seconds": 30
},
"requirements": {
"cpu": {
"architecture": "x86_64"
},
"node": {
"node_hash": "4d4db19afca380fdf06ba7f916153d0f740db9de9eee23ad26ba96a90d8a2920"
}
},
"rootfs": {
"parent": {
"ref": "549ec451d9b099cad112d4aaa2c00ac40fb6729a92ff252ff22eef0b5c3cb613",
"use_latest": true
},
"persistence": "host",
"size_mib": 20000
},
"authorized_keys": [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGULT6A41Msmw2KEu0R9MvUjhuWNAsbdeZ0DOwYbt4Qt user@example",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH0jqdc5dmt75QhTrWqeHDV9xN8vxbgFyOYs2fuQl7CI",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRsrQV1HVrcnskNhyH0may8TG9fHCPawpAi3ZgAWU6V/R7ezvZOHnZdaFeIsOpFbPbt/l67Fur3qniSXllI2kvuh2D4BBJ9PwwlB2sgWzFDF34ADsfLQf+C/vpwrWemEEE91Tpj0dWbnf219i3mZLxy/+5Sv6kUy9YJlzWnDEUbaMAZK2CXrlK90b9Ns7mT82h6h3x9dLF/oCjBAKOSxbH2X+KgsDEZT0soxyluDqKNgKflkav+pvKFyD4J9IWM4j36r80yW+OPGsHqWoWleEhprfNb60RJPwKAYCDiBiSg6wCq5P+kS15O79Ko45wPaYDUwhRoNTcrWeadvTaCZgz9X3KDHgrX6wzdKqzQwtQeabhCaIGLFRMNl1Oy/BR8VozPbIe/mY28IN84An50UYkbve7nOGJucKc4hKxZKEVPpnVpRtIoWGwBJY2fi6C6wy2pBa8UX4C4t9NLJjNQSwFBzYOrphLu3ZW9A+267nogQHGnsJ5xnQ/MXximP3BlwM= user@example"
],
"volumes": [
{
"comment": "Python libraries. Read-only since a 'ref' is specified.",
"mount": "/opt/venv",
"ref": "5f31b0706f59404fad3d0bff97ef89ddf24da4761608ea0646329362c662ba51",
"use_latest": false
},
{
"comment": "Ephemeral storage, read-write but will not persist after the VM stops",
"mount": "/var/cache",
"ephemeral": true,
"size_mib": 5
},
{
"comment": "Working data persisted on the VM supervisor, not available on other nodes",
"mount": "/var/lib/sqlite",
"name": "sqlite-data",
"persistence": "host",
"size_mib": 10
},
{
"comment": "Working data persisted on the Aleph network. New VMs will try to use the latest version of this volume, with no guarantee against conflicts",
"mount": "/var/lib/statistics",
"name": "statistics",
"persistence": "store",
"size_mib": 10
},
{
"comment": "Raw drive to use by a process, do not mount it",
"name": "raw-data",
"persistence": "host",
"size_mib": 10
}
],
"replaces": "0x9319Ad3B7A8E0eE24f2E639c40D8eD124C5520Ba",
"time": 1619017773.8950517
},
"item_type": "inline",
"signature": "0x372da8230552b8c3e65c05b31a0ff3a24666d66c575f8e11019f62579bf48c2b7fe2f0bbe907a2a5bf8050989cdaf8a59ff8a1cbcafcdef0656c54279b4aa0c71b",
"size": 749,
"time": 1619017773.8950577,
"confirmations": [
{
"chain": "ETH",
"height": 12284734,
"hash": "0x67f2f3cde5e94e70615c92629c70d22dc959a118f46e9411b29659c2fce87cdc"
}
]
}
43 changes: 39 additions & 4 deletions aleph_message/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
create_new_message,
parse_message,
)
from aleph_message.models.execution.environment import AMDSEVPolicy
from aleph_message.tests.download_messages import MESSAGES_STORAGE_PATH

console = Console(color_system="windows")
Expand Down Expand Up @@ -130,7 +131,7 @@ def test_post_content():


def test_message_machine():
path = Path(os.path.abspath(os.path.join(__file__, "../messages/machine.json")))
path = Path(__file__).parent / "messages/machine.json"
message = create_message_from_file(path, factory=ProgramMessage)

assert isinstance(message, ProgramMessage)
Expand All @@ -140,13 +141,47 @@ def test_message_machine():


def test_instance_message_machine():
path = Path(
os.path.abspath(os.path.join(__file__, "../messages/instance_machine.json"))
)
path = Path(__file__).parent / "messages/instance_machine.json"
message = create_message_from_file(path, factory=InstanceMessage)

assert isinstance(message, InstanceMessage)
assert hash(message.content)


def test_instance_message_machine_with_confidential_options():
path = Path(__file__).parent / "messages/instance_confidential_machine.json"
message = create_message_from_file(path, factory=InstanceMessage)

assert isinstance(message, InstanceMessage)
assert hash(message.content)
assert message.content.environment.trusted_execution
assert message.content.environment.trusted_execution.policy == AMDSEVPolicy.NO_DBG
assert (
message.content.environment.trusted_execution.firmware
== "e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317"
)
assert message.content.requirements and message.content.requirements.node
assert (
message.content.requirements.node.node_hash
== "4d4db19afca380fdf06ba7f916153d0f740db9de9eee23ad26ba96a90d8a2920"
)


def test_validation_on_confidential_options():
"""Ensure that a trusted environment is only allowed for QEmu."""
path = Path(__file__).parent / "messages/instance_confidential_machine.json"
message_dict = json.loads(path.read_text())
# Patch the hypervisor to be something other than QEmu
message_dict["content"]["environment"]["hypervisor"] = "firecracker"
try:
_ = create_new_message(message_dict, factory=InstanceMessage)
raise AssertionError("An exception should have been raised before this point.")
except ValidationError as e:
assert e.errors()[0]["loc"] == ("content", "environment", "trusted_execution")
assert (
e.errors()[0]["msg"]
== "Trusted Execution Environment is only supported for QEmu"
)


def test_message_machine_port_mapping():
Expand Down
Loading