Skip to content
3 changes: 2 additions & 1 deletion crates/contract/assets/launcher_docker_compose.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ version: '3.8'

services:
launcher:
image: nearone/mpc-launcher@sha256:7f155ebc928579ca269840d2e59f2e08e3c0cc1271c586f2e2b74bbda27ee8e1
image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3

container_name: launcher

environment:
- PLATFORM=TEE
- DOCKER_CONTENT_TRUST=1
- DEFAULT_IMAGE_DIGEST=sha256:{{DEFAULT_IMAGE_DIGEST_HASH}}

Expand Down
84 changes: 75 additions & 9 deletions tee_launcher/launcher.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from collections import deque
import logging
import os
import stat
from typing import Dict, Union
from enum import Enum
import requests
from subprocess import CompletedProcess, run, check_output
import sys
Expand Down Expand Up @@ -36,6 +38,18 @@ class RpcTimingConfig(NamedTuple):
rpc_max_attempts: int


#
# Platform mode (MUST come from measured docker-compose env in TEE deployments)
#
ENV_VAR_PLATFORM = "PLATFORM"


# do not change the string values - they are used in docker-compose files.
class Platform(Enum):
TEE = "TEE"
NONTEE = "NONTEE"


Comment thread
barakeinav1 marked this conversation as resolved.
Resolved
# only considered if `IMAGE_DIGEST_FILE` does not exist.
ENV_VAR_DEFAULT_IMAGE_DIGEST = "DEFAULT_IMAGE_DIGEST"

Expand Down Expand Up @@ -270,6 +284,38 @@ def parse_env_file(path: str) -> dict:
return parse_env_lines(f.readlines())


def is_unix_socket(path: str) -> bool:
try:
st = os.stat(path)
return stat.S_ISSOCK(st.st_mode)
except FileNotFoundError:
return False
except Exception:
return False


def parse_platform() -> Platform:
"""
Platform selection MUST be a measured input in TEE deployments.
Therefore, we only read it from process env (docker-compose 'environment'),
and never from /tapp/user_config.
"""
raw = os.environ.get(ENV_VAR_PLATFORM)
if raw is None:
raise RuntimeError(
f"{ENV_VAR_PLATFORM} must be set to one of {[p.value for p in Platform]}"
)

val = raw.strip()
try:
return Platform(val)
except ValueError as e:
allowed = ", ".join(p.value for p in Platform)
raise RuntimeError(
f"Invalid {ENV_VAR_PLATFORM}={raw!r}. Expected one of: {allowed}"
) from e


def load_rpc_timing_config(dstack_config: dict[str, str]) -> RpcTimingConfig:
"""
Loads dockerhub RPC timing configuration from dstack_config,
Expand Down Expand Up @@ -440,7 +486,16 @@ def validate_image_hash(
return False


def extend_rtmr3(valid_hash: str) -> None:
def extend_rtmr3(platform: Platform, valid_hash: str) -> None:
if platform == Platform.NONTEE:
logging.info("PLATFORM=NONTEE → skipping RTMR3 extension step.")
return

if not is_unix_socket(DSTACK_UNIX_SOCKET):
raise RuntimeError(
f"PLATFORM=TEE requires dstack unix socket at {DSTACK_UNIX_SOCKET}."
)
Comment on lines +494 to +497

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is orthogonal to the issue? Something missing from before?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related, not orthogonal. (let says 42 degrees)
If we don't have DSTACK_UNIX_SOCKET when running in TEE mode, this is a critical failure.
better to stop right away.


bare = get_bare_digest(valid_hash)
logging.info(f"Extending RTMR3 with validated hash: {bare}")

Expand All @@ -460,11 +515,11 @@ def extend_rtmr3(valid_hash: str) -> None:
raise RuntimeError("EmitEvent failed while extending RTMR3")


def launch_mpc_container(valid_hash: str, user_env: dict) -> None:
def launch_mpc_container(platform: Platform, valid_hash: str, user_env: dict) -> None:
logging.info(f"Launching MPC node with validated hash: {valid_hash}")

remove_existing_container()
docker_cmd = build_docker_cmd(user_env, valid_hash)
docker_cmd = build_docker_cmd(platform, user_env, valid_hash)

proc = run(docker_cmd)
if proc.returncode != 0:
Expand Down Expand Up @@ -510,6 +565,14 @@ def curl_unix_socket_post(
def main():
logging.info("start")

platform = parse_platform()
logging.info(f"Launcher platform: {platform.value}")

if platform is Platform.TEE and not is_unix_socket(DSTACK_UNIX_SOCKET):
raise RuntimeError(
f"PLATFORM=TEE requires dstack unix socket at {DSTACK_UNIX_SOCKET}."
)

# DOCKER_CONTENT_TRUST must be enabled
if os.environ.get(OS_ENV_DOCKER_CONTENT_TRUST, "0") != "1":
raise RuntimeError(
Expand Down Expand Up @@ -547,9 +610,9 @@ def main():
# Continue with validated hash
logging.info(f"MPC image hash validated successfully: {selected_hash}")

extend_rtmr3(selected_hash)
extend_rtmr3(platform, selected_hash)

launch_mpc_container(selected_hash, dstack_config)
launch_mpc_container(platform, selected_hash, dstack_config)


def request_until_success(
Expand Down Expand Up @@ -685,15 +748,20 @@ def get_bare_digest(full_digest: str) -> str:
return full_digest.split(":", 1)[1]


def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]:
def build_docker_cmd(
platform: Platform, user_env: dict[str, str], image_digest: str
) -> list[str]:
bare_digest = get_bare_digest(image_digest)

docker_cmd = ["docker", "run"]

# Required environment variables
docker_cmd += ["--env", f"MPC_IMAGE_HASH={bare_digest}"]
docker_cmd += ["--env", f"MPC_LATEST_ALLOWED_HASH_FILE={IMAGE_DIGEST_FILE}"]
docker_cmd += ["--env", f"DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"]

if platform is Platform.TEE:
docker_cmd += ["--env", f"DSTACK_ENDPOINT={DSTACK_UNIX_SOCKET}"]
docker_cmd += ["-v", f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}"]

# MPC env vars and extra config
for key, value in user_env.items():
Expand Down Expand Up @@ -737,8 +805,6 @@ def build_docker_cmd(user_env: dict[str, str], image_digest: str) -> list[str]:
"-v",
"/tapp:/tapp:ro",
"-v",
f"{DSTACK_UNIX_SOCKET}:{DSTACK_UNIX_SOCKET}",
"-v",
"shared-volume:/mnt/shared",
"-v",
"mpc-data:/data",
Expand Down
3 changes: 2 additions & 1 deletion tee_launcher/launcher_docker_compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ version: '3.8'

services:
launcher:
image: nearone/mpc-launcher@sha256:4065f2fce41415962be92471a4e793ff5147b00b2784617c7e8098be2761a875
image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3

container_name: launcher

environment:
- PLATFORM=TEE
- DOCKER_CONTENT_TRUST=1
- DEFAULT_IMAGE_DIGEST=sha256:00006c1059cc0219005b21956a4df8238b0cc33ad559a578a63169de4e28c81e

Expand Down
30 changes: 30 additions & 0 deletions tee_launcher/launcher_docker_compose_nontee.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: '3.8'

services:
launcher:
image: nearone/mpc-launcher@sha256:17243e65f47c69a4735c507ea91aefa0bdd761d9a267e28221232d748518bad3
container_name: launcher

environment:
- PLATFORM=NONTEE
- DOCKER_CONTENT_TRUST=1
- DEFAULT_IMAGE_DIGEST=sha256:00006c1059cc0219005b21956a4df8238b0cc33ad559a578a63169de4e28c81e

volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./user-config.conf:/tapp/user_config:ro
Comment thread
gilcu3 marked this conversation as resolved.
Resolved
- shared-volume:/mnt/shared
- mpc-data:/data

security_opt:
- no-new-privileges:true

read_only: true
tmpfs:
- /tmp

volumes:
shared-volume:
name: shared-volume
mpc-data:
name: mpc-data
Comment thread
gilcu3 marked this conversation as resolved.
Resolved
Loading