From e4103cb64c09f0513ad359ba418f15188f791ad1 Mon Sep 17 00:00:00 2001 From: Nikos Alexiou Date: Sun, 22 Feb 2026 22:48:18 +0200 Subject: [PATCH 1/3] Added SGX container example --- README.md | 202 +++++++------ examples/sgx_container/Dockerfile | 87 ++++++ examples/sgx_container/README.md | 83 ++++++ .../sgx_container/attest-cas-upload_policy.sh | 74 +++++ examples/sgx_container/build-function.sh | 34 +++ examples/sgx_container/dsd.sh | 24 ++ .../generate-enclave-signing-key.sh | 27 ++ .../sgx_container/get-function-mrenclave.sh | 23 ++ .../install-intel-pckidrt-debian12.sh | 115 ++++++++ .../sgx_container/install-retrieval-tool.sh | 30 ++ .../modified_files/container_devices.rs | 24 ++ .../modified_files/container_function.rs | 269 ++++++++++++++++++ examples/sgx_container/policy.yaml | 39 +++ examples/sgx_container/provision-cas.sh | 66 +++++ examples/sgx_container/reset-cas.sh | 40 +++ .../sgx_container/scone-list-registry-tags.sh | 51 ++++ examples/sgx_container/setup-and-run-pccs.sh | 165 +++++++++++ examples/sgx_container/setup-cas.sh | 187 ++++++++++++ .../setup-intel-sgx-dcap-pckidrt-debian12.sh | 126 ++++++++ examples/sgx_container/setup-las.sh | 79 +++++ examples/sgx_container/sgx-groups.sh | 53 ++++ examples/sgx_container/sgx_default_qcnl.conf | 4 + examples/sgx_container/workflow.json | 20 ++ 23 files changed, 1734 insertions(+), 88 deletions(-) create mode 100644 examples/sgx_container/Dockerfile create mode 100644 examples/sgx_container/README.md create mode 100644 examples/sgx_container/attest-cas-upload_policy.sh create mode 100644 examples/sgx_container/build-function.sh create mode 100644 examples/sgx_container/dsd.sh create mode 100644 examples/sgx_container/generate-enclave-signing-key.sh create mode 100644 examples/sgx_container/get-function-mrenclave.sh create mode 100644 examples/sgx_container/install-intel-pckidrt-debian12.sh create mode 100644 examples/sgx_container/install-retrieval-tool.sh create mode 100644 examples/sgx_container/modified_files/container_devices.rs create mode 100644 examples/sgx_container/modified_files/container_function.rs create mode 100644 examples/sgx_container/policy.yaml create mode 100644 examples/sgx_container/provision-cas.sh create mode 100644 examples/sgx_container/reset-cas.sh create mode 100644 examples/sgx_container/scone-list-registry-tags.sh create mode 100644 examples/sgx_container/setup-and-run-pccs.sh create mode 100644 examples/sgx_container/setup-cas.sh create mode 100644 examples/sgx_container/setup-intel-sgx-dcap-pckidrt-debian12.sh create mode 100644 examples/sgx_container/setup-las.sh create mode 100644 examples/sgx_container/sgx-groups.sh create mode 100644 examples/sgx_container/sgx_default_qcnl.conf create mode 100644 examples/sgx_container/workflow.json diff --git a/README.md b/README.md index 97c6b598..17dd458d 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,156 @@ -![](documentation/edgeless-logo-alpha-200.png) +# EDGELESS SGX Container Function Example (SCONE) -# EDGELESS Reference Implementation -This repository contains a research prototype of the EDGELESS platform, which is -under active development within the project -[EDGELESS](https://edgeless-project.eu/) (2023-2025). +This example demonstrates how to run **EDGELESS** functions inside +**Intel SGX–protected containers** using **SCONE** and the EDGELESS +**container runtime**. -## Introduction +It provides a minimal, reproducible setup that: +- Builds a **Rust-based function** as a container image +- Cross-compiles the function with **SCONE** +- Includes scripts to setup a local PCCS/DCAP service, SCONE's LAS and CAS +- Includes a workflow containing a **single function** -EDGELESS is a framework that enables -[stateful FaaS agents](documentation/serverless_edge_computing.md) and -is intended especially for edge nodes with limited computational capabilities. +--- -An EDGELESS cluster is managed by an ε-CON (controller) and consists of one -or more _orchestration domains_, each managed by an ε-ORC (orchestrator). +## Prerequisites -The ε-CON allows clients to request the creation of edge services (called -_workflows_), which consist of a collection of interconnected _functions_ -and _resources_ and related annotations. -The management of the lifecycle of any such logical functions/resources is -delegated by the ε-CON to one ε-ORC, which, in turn, manages the lifecycle -of physical function/resource instances on EDGELESS nodes. +To reproduce this example, the following requirements must be met. -Each EDGELESS node may offer multiple run-time environments (e.g., WebAssembly -or Docker) to run function instances and resource providers of different types -(e.g., file logs or Kafka) to create resource instances. +### 1. Local EDGELESS Build -Orchestration in EDGELESS happens at two levels: +You must build EDGELESS locally and configure it appropriately: -- _higher level orchestration_ is done by the ε-CON at cluster level - and it maps logical functions/resources to orchestration domains; -- _lower level orchestration_ is done by the ε-ORC within its orchestration - domain, and it maps every logical function/resource to physical instances - on its nodes. +- Enable the **container runtime** in `node.toml` +- Set `guest_api_host_url` to an address **reachable from inside containers** + - ❌ Do **not** use `127.0.0.1` or `0.0.0.0` -## How to build +--- -See [building instructions](BUILDING.md). +### 2. SCONE Infrastructure Access -## Quick start +Access to the SCONE container registry is required in order to pull the appropriate images. -It is recommended that you enable at least info-level log directives with: +1. Register at +2. Create a **Personal Access Token** with appropriate permissions +3. Log in to the SCONE registry: -```shell -export RUST_LOG=info +```bash +docker login registry.scontain.com +# username: +# password: ``` +___ +## Function Logic -To get the basic system running, first create the default configuration files -(they have fixed hardcoded values): +The function logic must be implemented in: -```shell -target/debug/edgeless_inabox -t -target/debug/edgeless_cli -t cli.toml +```text +edgeless/edgeless_container_function/src/container_function.rs ``` +Folder layout: + +```text +~/edgeless/ +├── edgeless_container_function/ +│ └── src/container_function.rs <- Add your function logic here +├── edgeless_api/ +├── Cargo.toml +├── Cargo.lock +└── examples/ + └── sgx_container/ + ├── Dockerfile <- Dockerfile lives here + ├── build.sh + ├── workflow.json + ├── ... + └── modified_files <- files that were modified to run the example +``` +### About modified files: +- `container_devices.rs` goes to `edgeless_node/src/container_runner/` in order to choose the in-tree SGX driver + +- `container_function.rs`: implements the function logic, see folder structure above. +___ +## Building the function image -which will create: +To build the image, first generate an enclave signing private RSA key for scone-signer: + +```bash +./generate-enclave-signing-key.sh +``` +Then use the provided build script: +```bash +./build.sh +``` +___ +## Setup SGX Provisioning Certificate Caching Service (PCCS) -- `controller.toml` -- `node.toml` -- `orchestrator.toml` -- `cli.toml` +Use the provided script to start a PCCS Docker container, generate and save admin/user auth tokens, patch the PCCS config with your Intel API key and token hashes, verify the service is reachable, and, optionally register the host platform with PCKIDRetrievalTool. -Then you can run the **EDGELESS-in-a-box**, which is a convenience binary that -runs every necessary component as one, using the generated configuration files: +```bash +./setup-and-run-pccs.sh ``` -target/debug/edgeless_inabox + +--- + +## Setup and run SCONE's Local Attestation Service (LAS) + +LAS runs on the node. It helps SGX apps create attestation quotes and connects them to the attestation infrastructure (like PCCS), so the enclave can prove it’s genuine and running trusted code. + +```bash +./setup-las.sh +docker compose -f docker-las-compose.yml up -d ``` +___ -Congratulations 🎉 now that you have a complete EDGELESS system you may check -our workflows/function examples, which are representative of the current -EDGELESS features: +## Setup and run SCONE's Configuration and Attestation Service (CAS) +The role of CAS is to verify enclave attestation and securely deliver the function’s secrets only to trusted enclaves. -- [Example workflows](examples/README.md) -- [Example functions](functions/README.md) +### CAS setup and run -## Next steps +```bash +./setup-cas.sh +docker compose -f docker-cas-compose.yml up -d +``` -Basics: +This script sets up CAS locally by preparing the CAS config files, detecting SGX devices and host SGX-related group IDs, generating the Docker Compose setup (including cas-init), computing and saving the CAS hash (SCONE_HASH=1), and mounting sgx_default_qcnl.conf if it exists next to the script. -- [Workflows, resources, and functions](documentation/basic_concepts.md) -- [ε-CON](documentation/controller.md) -- [ε-ORC](documentation/orchestrator.md) -- [EDGELESS node](documentation/node.md) -- [EDGELESS command-line clients](documentation/cli.md) -- [A step-by-step example](documentation/deploy_step_by_step.md) +### SCONE CAS provisioning -Advanced topics: +CAS must first be provisioned, meaning it is initialized as a trusted service (with its own identity/keys) so it can securely manage secrets and attestation decisions. -- [Repository layout](documentation/repository_layout.md) -- [EDGELESS APIs](edgeless_api/README.md) -- [How to create a new function](documentation/rust_functions.md) -- [Local orchestration](documentation/local_orchestration.md) -- [Benchmarking EDGELESS](documentation/benchmark.md) -- [Docker container runtime](documentation/container-runtime.md) -- [A multi-domain example](documentation/example_multidomain.md) -- [Inter-domain workflows](documentation/interdomain_workflows.md) -- [(m)TLS](edgeless_api/src/grpc_impl/tls_certs/README.md) +```bash +./provision-cas.sh +``` -## Known limitations +The script provisions CAS in HW mode by detecting SGX devices/groups, asks whether to do attested provisioning, and then provisions cas in a Docker container using LAS and your provisioning token/key hash (optionally verifying CAS via CAS_MRENCLAVE). +___ -⚠️ There currently are no guarantees on stability, this software is not -(yet) intended for production deployments. +### Policy upload to CAS -The open issues are tracked on -[GitHub](https://github.com/edgeless-project/edgeless/issues). +After provisioning, CAS is ready to accept policies (i.e. security configuration and rules for apps). CAS uses those policies to decide which enclaves are allowed to receive secrets based on enclave identity measurements like: -Stay tuned (star & watch -[the GitHub project](https://github.com/edgeless-project/edgeless)) to remain -up to date on future developments. +- MRENCLAVE: the exact enclave code hash (specific build) +- MRSIGNER: the signer identity (who signed the enclave), allowing trust across compatible versions signed by the same key -## Contributing -We love the open source community of developers ❤️ and we welcome contributions -to EDGELESS. +```bash +./attest-cas-upload_policy.sh +``` -The [contributing guide](CONTRIBUTING_GUIDE.md) contains some rules you should -adhere to when contributing to this repository. +This script attests CAS (optionally pinned to a specific MRENCLAVE), uploads a policy/session file to it, and finally verifies that the policy was stored successfully. + +A dummy `policy.yml` file is provided. Also, in case you want to completely remove CAS by deleting the CAS container, remove the CAS data volume, and clean the generated local CAS files/artifacts, use: + +```bash +./reset-cas.sh +``` +___ -## License -The Repository is licensed under the MIT License. Please refer to -[LICENSE](LICENSE) and [CONTRIBUTORS.txt](CONTRIBUTORS.txt). +## Notes +1. SCONE cross-compilation currently uses an **older EDGELESS commit** that does **not** depend on `aws-lc-sys`. Newer versions introduce a dependency on `aws-lc-sys`, a low-level +crate wrapping C and assembly code, which prevents successful cross-compilation. This does not refer to the local copy of your EDGELESS but to the commit brought in the build stage. -## Funding +2. The image name *must* contain `edgeless-sgx-function- ` otherwise EDGELESS will not pass the sgx driver to docker. See [docker_utils.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/docker_utils.rs) and [container_devices.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/container_devices.rs) -EDGELESS received funding from the [European Health and Digital Executive Agency -(HADEA)](https://hadea.ec.europa.eu/) program under Grant Agreement No -101092950. diff --git a/examples/sgx_container/Dockerfile b/examples/sgx_container/Dockerfile new file mode 100644 index 00000000..61dcc933 --- /dev/null +++ b/examples/sgx_container/Dockerfile @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +FROM registry.scontain.com/sconecuratedimages/crosscompilers:ubuntu24.04-scone6.0.5 AS builder + +WORKDIR /myapp + +RUN apt-get update && apt-get install -y \ + git pkg-config libtss2-dev protobuf-compiler \ + && rm -rf /var/lib/apt/lists/* + +ENV PKG_CONFIG_ALLOW_CROSS=1 + +RUN git clone https://github.com/edgeless-project/edgeless.git \ + && cd edgeless && git checkout 7412d3b^ && cd .. + +COPY edgeless_container_function/src/container_function.rs \ + /myapp/edgeless/edgeless_container_function/src/container_function.rs + +WORKDIR /myapp/edgeless/edgeless_container_function + +# Building the NON-debug binary +RUN scone-cargo build -p edgeless_container_function --release --verbose + +# Check if the binary has SCONE-specific symbols. If missing, the binary will run as a "native" app and ignore SCONE_CONFIG_ID. +RUN nm /myapp/edgeless/target/x86_64-scone-linux-musl/release/edgeless_container_function_d | grep -q scone_ || \ + (echo "ERROR: Binary is not SCONE-compiled!" && exit 1) + + +# ---- SIGNER AND RUNTIME STAGE ---- +FROM registry.scontain.com/sconecuratedimages/crosscompilers:runtime-scone6.0.5 AS runner + +WORKDIR /app + +# Copy the debug enclave binary produced by the crosscompiler +COPY --from=builder \ + /myapp/edgeless/target/x86_64-scone-linux-musl/release/edgeless_container_function_d \ + /usr/local/bin/edgeless_container_function + +# Create a production-signed (non-debug) enclave binary. +# CAUTION: If you ship a debug enclave ("*_d"), CAS will treat it as untrustworthy and refuse to release secrets/config. +# BuildKit secret "enclave_key" is mounted at /run/secrets/enclave_key FOR THIS STEP ONLY and is NOT stored in any image layer. +# Use && to ensure the build ABORTS if any step fails. +# We also check if the secret file actually exists before trying to use it. + +RUN --mount=type=secret,id=enclave_key \ + set -e; \ + export SCONE_SIGNER_VERBOSE=1; \ + \ + if [ ! -f /run/secrets/enclave_key ]; then \ + echo "ERROR: enclave_key secret not found! Did you forget --secret? Use --secret id=enclave_key,src=path/to/key"; \ + exit 1; \ + fi; \ + \ + echo "== BEFORE SIGNING =="; \ + scone-signer info /usr/local/bin/edgeless_container_function | head -n 20; \ + \ + echo "== SIGNING ENCLAVE =="; \ + scone-signer sign --production --key /run/secrets/enclave_key /usr/local/bin/edgeless_container_function; \ + \ + echo "== AFTER SIGNING =="; \ + scone-signer info /usr/local/bin/edgeless_container_function > /tmp/sign_info; \ + head -n 40 /tmp/sign_info; \ + echo "--- Verification ---"; \ + # Check the Public Key Modulus (Ensures it was signed by YOUR key) + grep -A 2 "Modulus:" /tmp/sign_info; \ + # Verify Debug is 0 (required for --production) + grep "Debug:" /tmp/sign_info; \ + # Extract the Enclave Measurement (MRENCLAVE) + grep "MRENCLAVE:" /tmp/sign_info; \ + # File hash for audit trail + sha256sum /usr/local/bin/edgeless_container_function; \ + rm /tmp/sign_info + +EXPOSE 7101 + +ENV RUST_LOG=info \ + SCONE_MODE=hw \ + SCONE_VERSION=1 \ + SCONE_LAS_ADDR=172.17.0.1:18766 \ + SCONE_CAS_ADDR=172.17.0.1:18765 \ + SCONE_CONFIG_ID=policy/edgeless_function + +# NOTE: These addresses use the Docker host bridge IP (172.17.0.1). This assumes LAS/CAS ports are published on the host +# and reachable from the container via the default Docker bridge routing. + +CMD ["edgeless_container_function", "--endpoint", "http://0.0.0.0:7101"] diff --git a/examples/sgx_container/README.md b/examples/sgx_container/README.md new file mode 100644 index 00000000..3dfda061 --- /dev/null +++ b/examples/sgx_container/README.md @@ -0,0 +1,83 @@ +# EDGELESS SGX Container Function Example (SCONE) + + +This example demonstrates how to run **EDGELESS** functions inside +**Intel SGX–protected containers** using **SCONE** and the EDGELESS +**container runtime**. + +It provides a minimal, reproducible setup that: +- Builds a **Rust-based function** as a container image +- Cross-compiles the function with **SCONE** +- Includes a workflow containing a **single function** + +--- + +## Prerequisites + +To reproduce this example, the following requirements must be met. + +### 1. Local EDGELESS Build + +You must build EDGELESS locally and configure it appropriately: + +- Enable the **container runtime** in `node.toml` +- Set `guest_api_host_url` to an address **reachable from inside containers** + - ❌ Do **not** use `127.0.0.1` or `0.0.0.0` + +--- + +### 2. SCONE Infrastructure Access + +Access to the SCONE container registry is required in order to pull images. + +1. Register at +2. Create a **Personal Access Token** with appropriate permissions +3. Log in to the SCONE registry: + +```bash +docker login registry.scontain.com +# username: +# password: +``` +___ +## Function Logic + +The function logic must be implemented in: + +```text +edgeless/edgeless_container_function/src/container_function.rs +``` +Folder layout: + +```text +~/edgeless/ +├── edgeless_container_function/ +│ └── src/container_function.rs <- Add your function logic here +├── edgeless_api/ +├── Cargo.toml +├── Cargo.lock +└── examples/ + └── sgx_container/ + ├── Dockerfile <- Dockerfile lives here + ├── build.sh + └── workflow.json +``` + +___ +## Build and run + +```bash +user@host:~/edgeless$ examples/sgx_container/build.sh +user@host:~/edgeless$ target/debug/edgeless_cli workflow start examples/sgx_container/workflow.json +``` + + +--- + +## Notes +1. SCONE cross-compilation currently uses an **older EDGELESS commit** that does **not** depend on `aws-lc-sys`. Newer versions introduce a dependency on `aws-lc-sys`, a low-level +crate wrapping C and assembly code, which prevents successful cross-compilation. This does not refer to the local copy of your EDGELESS but to the commit brought in the build stage. + +2. A one-stage Dockerfile variant is provided -with the relevant workflow & build script- for testing purposes eg measure function instantiation times when images are much larger. The default two-stage build achieves up to **10× smaller images**, typically reducing image sizes from a few GB to hundreds of MB. + +3. The image name *must* contain `edgeless-sgx-function- ` otherwise EDGELESS will not pass the sgx driver to docker. Go to [docker_utils.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/docker_utils.rs) and [container_devices.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/container_devices.rs) diff --git a/examples/sgx_container/attest-cas-upload_policy.sh b/examples/sgx_container/attest-cas-upload_policy.sh new file mode 100644 index 00000000..0334128a --- /dev/null +++ b/examples/sgx_container/attest-cas-upload_policy.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# Attest CAS and upload a policy/session to it. +# Works with debug CAS using "only_for_testing" flags. + +POLICY_FILE="${1:-policy.yaml}" +CAS_ADDR="${CAS_ADDR:-cas:8081}" +LAS_ADDR="${LAS_ADDR:-las:18766}" +SCONE_NET="${SCONE_NET:-scone-net}" +CLI_IMAGE="${CLI_IMAGE:-registry.scontain.com/sconecuratedimages/crosscompilers:ubuntu24.04-scone6.0.5}" +CAS_MRENCLAVE="${CAS_MRENCLAVE:-}" + +if [[ ! -f "$POLICY_FILE" ]]; then + echo "ERROR: Policy file not found: $POLICY_FILE" >&2 + exit 1 +fi + +echo "--- Configuration ---" +echo "CAS_ADDR: $CAS_ADDR" +echo "LAS_ADDR: $LAS_ADDR" +echo "SCONE_NET: $SCONE_NET" +echo "POLICY_FILE: $POLICY_FILE" + +if [[ -n "$CAS_MRENCLAVE" ]]; then + echo "CAS_MRENCLAVE pinned: $CAS_MRENCLAVE" +else + echo "CAS_MRENCLAVE not set -> using --only_for_testing-trust-any (debug CAS)" +fi +echo "----------------------" + +# We mount $PWD to /side so the policy file is visible inside the CLI container. +# We mount ~/.cas to /root/.cas because the SCONE CLI stores its client identity + attestation cache there. +# NOTE: Without --user, this container runs as root and may create/overwrite root-owned files in ~/.cas on the host. +docker run --rm -it \ + --network "$SCONE_NET" \ + -v "$PWD:/side" \ + -v "$HOME/.cas:/root/.cas" \ + -w /side \ + -e "SCONE_CAS_ADDR=$CAS_ADDR" \ + -e "SCONE_LAS_ADDR=$LAS_ADDR" \ + -e "CAS_MRENCLAVE=$CAS_MRENCLAVE" \ + -e "POLICY_FILE=$POLICY_FILE" \ + "$CLI_IMAGE" \ + bash -lc ' + set -euo pipefail + + ATTEST_OPTS=( + --only_for_testing-ignore-signer + --only_for_testing-debug + --accept-group-out-of-date + --accept-configuration-needed + ) + + echo "Attesting CAS" + if [[ -n "${CAS_MRENCLAVE:-}" ]]; then + scone cas attest "${ATTEST_OPTS[@]}" --mrenclave "${CAS_MRENCLAVE}" "${SCONE_CAS_ADDR}" + else + scone cas attest "${ATTEST_OPTS[@]}" --only_for_testing-trust-any "${SCONE_CAS_ADDR}" + fi + + echo "Creating session from policy" + scone session create --cas="${SCONE_CAS_ADDR}" "/side/${POLICY_FILE}" + + echo "Verifying stored session in CAS" + scone session read --cas="${SCONE_CAS_ADDR}" policy \ + | grep -nE "name:|version:|mrenclaves:|DEMO_SECRET|SCONE_ALLOW_DLOPEN|SCONE_LOG" || true + + echo "Success: Policy uploaded." + ' + diff --git a/examples/sgx_container/build-function.sh b/examples/sgx_container/build-function.sh new file mode 100644 index 00000000..4d77674b --- /dev/null +++ b/examples/sgx_container/build-function.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# Go to repo root (two levels up from examples/sgx_container) +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# CAUTION: only images whose name contains edgeless-sgx-function- will get the SGX device mapping +# see edgeless/edgeless_node/src/container_runner/docker_utils.rs +IMAGE_TAG="edgeless-sgx-function-rust:dev" + +KEY_FILE="${SCRIPT_DIR}/enclave-key.pem" + +echo "Building container function image: ${IMAGE_TAG}" +echo "Repo root: ${REPO_ROOT}" +echo "Signing key: ${KEY_FILE}" + +if [[ ! -f "$KEY_FILE" ]]; then + echo "ERROR: Signing key not found at: $KEY_FILE" + echo "Generate it first with ./gen-enclave-key.sh)" + exit 1 +fi + +DOCKER_BUILDKIT=1 docker build --progress=plain --no-cache \ + --secret id=enclave_key,src="$KEY_FILE" \ + -t "${IMAGE_TAG}" \ + -f "${REPO_ROOT}/examples/sgx_container/Dockerfile" \ + "${REPO_ROOT}" 2>&1 | tee "${SCRIPT_DIR}/build.log" + +echo "Build complete." + diff --git a/examples/sgx_container/dsd.sh b/examples/sgx_container/dsd.sh new file mode 100644 index 00000000..ccf0332f --- /dev/null +++ b/examples/sgx_container/dsd.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +# Determine SGX device nodes. Usage: source ./dsd.sh + +export SGX_ENCLAVE_DEV="" +export SGX_PROVISION_DEV="" + +# Detect Enclave Device +if [[ -c /dev/sgx_enclave ]]; then + export SGX_ENCLAVE_DEV="/dev/sgx_enclave" +elif [[ -c /dev/sgx/enclave ]]; then + export SGX_ENCLAVE_DEV="/dev/sgx/enclave" +elif [[ -c /dev/isgx ]]; then + export SGX_ENCLAVE_DEV="/dev/isgx" +fi + +# Detect Provision Device +if [[ -c /dev/sgx_provision ]]; then + export SGX_PROVISION_DEV="/dev/sgx_provision" +elif [[ -c /dev/sgx/provision ]]; then + export SGX_PROVISION_DEV="/dev/sgx/provision" +fi diff --git a/examples/sgx_container/generate-enclave-signing-key.sh b/examples/sgx_container/generate-enclave-signing-key.sh new file mode 100644 index 00000000..07a178b5 --- /dev/null +++ b/examples/sgx_container/generate-enclave-signing-key.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# Generates an SGX/SCONE enclave signing key (RSA) for scone-signer +# SCONE/SGX Requirement: 3072 bits and Public Exponent = 3 +KEY_FILE="${KEY_FILE:-enclave-key.pem}" +BITS="3072" # Fixed to SGX standard + +command -v openssl >/dev/null 2>&1 || { echo "ERROR: openssl not found"; exit 1; } + +# If the key already exists, don't overwrite it without warning +if [[ -f "$KEY_FILE" ]]; then + echo "Warning: $KEY_FILE already exists. Backing up to ${KEY_FILE}.bak" + mv "$KEY_FILE" "${KEY_FILE}.bak" +fi + +umask 077 +# The '-3' flag sets the public exponent to 3 +openssl genrsa -out "$KEY_FILE" -3 "$BITS" >/dev/null 2>&1 +chmod 600 "$KEY_FILE" + +echo "Success: Wrote SGX-compatible key to $(pwd)/$KEY_FILE" +echo "--- Verification ---" +openssl rsa -in "$KEY_FILE" -text -noout | grep -E "Public-Key|Exponent" diff --git a/examples/sgx_container/get-function-mrenclave.sh b/examples/sgx_container/get-function-mrenclave.sh new file mode 100644 index 00000000..4b552952 --- /dev/null +++ b/examples/sgx_container/get-function-mrenclave.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + + +LOG_FILE="build.log" + +#Extracts MRENCLAVEs, clean up the output and get the last two unique values (Non-EDMM is listed first in the log) +MRENCLAVES=$(sed -n '/== AFTER SIGNING ==/,$p' "$LOG_FILE" | \ + grep "MRENCLAVE:" | \ + grep -oE '[a-f0-9]{64}' | \ + tail -n 2) + +# Assign to variables +NON_EDMM=$(echo "$MRENCLAVES" | sed -n '1p') +EDMM=$(echo "$MRENCLAVES" | sed -n '2p') + +echo "Function MRENCLAVE go into the session/policy YAML that will be uploaded to CAS" +echo "" +echo "mrenclave:" +echo " - \"$EDMM\" # Use for SGX2 (Modern CPUs/Dynamic Memory Management)" +echo " - \"$NON_EDMM\" # Use for SGX1 (Legacy CPUs/Static Memory Management)" +echo "You can use both to ensure your function runs on any SGX hardware." diff --git a/examples/sgx_container/install-intel-pckidrt-debian12.sh b/examples/sgx_container/install-intel-pckidrt-debian12.sh new file mode 100644 index 00000000..1b0b30c1 --- /dev/null +++ b/examples/sgx_container/install-intel-pckidrt-debian12.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# Versions/URLs (from Intel DCAP "latest" Debian12 directory) +DCAP_BASE="https://download.01.org/intel-sgx/latest/dcap-latest/linux" +DEB12_DIR="$DCAP_BASE/distro/Debian12" +LOCAL_REPO_TGZ="sgx_debian_local_repo.tgz" +PCK_TGZ="PCKIDRetrievalTool_v1.24.100.2.tar.gz" +SHA_CFG="$DCAP_BASE/SHA256SUM_dcap_1.24.cfg" + +# Install locations +INTEL_OPT="/opt/intel" +REPO_DIR="$INTEL_OPT/sgx_debian_local_repo" +KEYRING_DIR="/etc/apt/keyrings" +KEYRING_FILE="$KEYRING_DIR/intel-sgx-keyring.asc" +APT_LIST="/etc/apt/sources.list.d/sgx_debian_local_repo.list" + +WORKDIR="${WORKDIR:-/tmp/intel-sgx-install}" +mkdir -p "$WORKDIR" +cd "$WORKDIR" + +need_cmd() { command -v "$1" >/dev/null 2>&1; } +as_root() { if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then echo "Run as root (sudo)."; exit 1; fi; } + +as_root + +# Base deps +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -y --no-install-recommends \ + ca-certificates curl gnupg tar gzip coreutils awk sed grep + +download() { + local url="$1" out="$2" + if [[ -f "$out" ]]; then + echo "Already have $out" + else + echo "Downloading $url" + curl -fsSLo "$out" "$url" + fi +} + +verify_sha256_from_cfg() { + local file="$1" cfg="$2" path_in_cfg="$3" + local local_sum remote_sum + local_sum="$(sha256sum "$file" | awk '{print $1}')" + remote_sum="$(curl -fsSL "$cfg" | tr ' ' '\n' | paste - - | awk -v p="$path_in_cfg" '$2==p {print $1}')" + + if [[ -z "$remote_sum" ]]; then + echo "ERROR: Could not find checksum entry for $path_in_cfg in $cfg" + exit 1 + fi + if [[ "$local_sum" != "$remote_sum" ]]; then + echo "ERROR: Checksum mismatch for $file" + echo " local: $local_sum" + echo " remote: $remote_sum" + exit 1 + fi + echo "Checksum OK for $file" +} + +# 1) Local repo tgz +download "$DEB12_DIR/$LOCAL_REPO_TGZ" "$LOCAL_REPO_TGZ" +verify_sha256_from_cfg "$LOCAL_REPO_TGZ" "$SHA_CFG" "distro/Debian12/$LOCAL_REPO_TGZ" + +mkdir -p "$INTEL_OPT" +# Re-extract cleanly each run to avoid stale repo content +rm -rf "$REPO_DIR" +tar xzf "$LOCAL_REPO_TGZ" -C "$INTEL_OPT" + +# 2) Add APT source (Debian 12 = bookworm) +mkdir -p "$KEYRING_DIR" +cp -f "$REPO_DIR/keys/intel-sgx.key" "$KEYRING_FILE" + +cat > "$APT_LIST" < $BIN_PATH" +fi + +echo +echo "Done." +echo "Verify:" +echo " systemctl status sgx-aesm.service --no-pager" +echo " PCKIDRetrievalTool || true" diff --git a/examples/sgx_container/install-retrieval-tool.sh b/examples/sgx_container/install-retrieval-tool.sh new file mode 100644 index 00000000..b9842908 --- /dev/null +++ b/examples/sgx_container/install-retrieval-tool.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# 1. Download and xtract to a system-wide path (/opt/intel) +echo "Downloading and extracting to /opt/intel" +curl -fsSLO https://download.01.org/intel-sgx/latest/linux-latest/distro/Debian12/sgx_debian_local_repo.tgz +sudo mkdir -p /opt/intel +sudo tar xzf sgx_debian_local_repo.tgz -C /opt/intel + +# 2. Fix Permissions (rucial for Debian 12 _apt user) +echo "Fixing permissions" +sudo chown -R root:root /opt/intel/sgx_debian_local_repo +sudo find /opt/intel/sgx_debian_local_repo -type d -exec chmod 755 {} + +sudo find /opt/intel/sgx_debian_local_repo -type f -exec chmod 644 {} + + +# 3. Setup Keyring +echo "Setting up keyring" +sudo mkdir -p /etc/apt/keyrings +sudo cp /opt/intel/sgx_debian_local_repo/keys/intel-sgx.key /etc/apt/keyrings/intel-sgx-keyring.asc + +# 4. Create the correct local source list +echo "deb [signed-by=/etc/apt/keyrings/intel-sgx-keyring.asc arch=amd64] file:///opt/intel/sgx_debian_local_repo bookworm main" | \ + sudo tee /etc/apt/sources.list.d/sgx_debian_local_repo.list + +# 5. Update and Install +sudo apt-get update +sudo apt-get install -y sgx-pck-id-retrieval-tool diff --git a/examples/sgx_container/modified_files/container_devices.rs b/examples/sgx_container/modified_files/container_devices.rs new file mode 100644 index 00000000..bc1716b4 --- /dev/null +++ b/examples/sgx_container/modified_files/container_devices.rs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: © 2024 Technical University of Crete +// SPDX-License-Identifier: MIT + +use rs_docker::container::DeviceStruct; + +pub fn get_sgx_out_of_tree_driver() -> DeviceStruct { + DeviceStruct { + CgroupPermissions: "rwm".to_string(), + PathOnHost: "/dev/isgx".to_string(), + PathInContainer: "/dev/isgx".to_string(), + } +} + +pub fn get_sgx_in_tree_driver() -> DeviceStruct { + DeviceStruct { + CgroupPermissions: "rwm".to_string(), + PathOnHost: "/dev/sgx_enclave".to_string(), + PathInContainer: "/dev/sgx_enclave".to_string(), + } +} + +pub fn get_sgx_nuc_driver() -> DeviceStruct { + get_sgx_in_tree_driver() +} diff --git a/examples/sgx_container/modified_files/container_function.rs b/examples/sgx_container/modified_files/container_function.rs new file mode 100644 index 00000000..248e0e56 --- /dev/null +++ b/examples/sgx_container/modified_files/container_function.rs @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: © 2024 Claudio Cicconetti +// SPDX-License-Identifier: MIT + +use edgeless_api::outer::container_runtime::ContainerRuntimeAPI; +use futures::{Future, SinkExt, StreamExt}; + +pub struct ContainerFunction { + sender: futures::channel::mpsc::UnboundedSender, +} + +enum ContainerFunctionRequest { + Boot( + edgeless_api::guest_api_function::BootData, + tokio::sync::oneshot::Sender>, + ), + Init( + edgeless_api::guest_api_function::FunctionInstanceInit, + tokio::sync::oneshot::Sender>, + ), + Cast(edgeless_api::guest_api_function::InputEventData), + Call( + edgeless_api::guest_api_function::InputEventData, + tokio::sync::oneshot::Sender>, + ), + Stop(), +} + +enum FiniteStateMachine { + PreBoot, + Booted, + Initialized, + Stopped, + Error, +} + +impl ContainerFunction { + pub fn new() -> (Self, std::pin::Pin + Send>>) { + log::debug!("new container function created"); + let (sender, receiver) = futures::channel::mpsc::unbounded(); + + let main_task = Box::pin(async move { + Self::main_task(receiver).await; + }); + + (Self { sender }, main_task) + } + + async fn main_task(receiver: futures::channel::mpsc::UnboundedReceiver) { + let mut receiver = receiver; + + // Always-visible diagnostic (doesn't depend on log backend) + eprintln!("MAIN_TASK started"); + + // Initialized in BOOT + let mut host_client; + let mut host_client_api = None; + + // InstanceID assigned to this function, initialized in BOOT + let mut instance_id = None; + + // Finite state machine of the function + let mut fsm = FiniteStateMachine::PreBoot; + + // Main loop that reacts to messages on the receiver channel + while let Some(req) = receiver.next().await { + match req { + ContainerFunctionRequest::Boot(boot_data, ack) => { + eprintln!( + "BOOT received (endpoint={}, instance_id={})", + boot_data.guest_api_host_endpoint, boot_data.instance_id + ); + + if std::mem::discriminant(&fsm) != std::mem::discriminant(&FiniteStateMachine::PreBoot) { + let _ = ack.send(Err(anyhow::anyhow!("boot received while not in PreBoot"))); + log::error!("received boot command while not in a pre-boot state: ignored"); + } else { + match edgeless_api::grpc_impl::outer::container_runtime::ContainerRuntimeAPIClient::new( + boot_data.guest_api_host_endpoint.as_str(), + None, + ) + .await + { + Ok(client) => { + host_client = Some(client); + host_client_api = Some(host_client.unwrap().guest_api_host()); + instance_id = Some(boot_data.instance_id); + fsm = FiniteStateMachine::Booted; + + let _ = ack.send(Ok(())); + eprintln!("BOOT done -> state Booted"); + } + Err(err) => { + log::error!("container function boot error: {}", err); + fsm = FiniteStateMachine::Error; + + let _ = ack.send(Err(anyhow::anyhow!("boot error: {}", err))); + eprintln!("BOOT error: {}", err); + } + } + } + } + ContainerFunctionRequest::Init(init_data, ack) => { + eprintln!( + "INIT received (payload_len={}, state_len={})", + init_data.init_payload.len(), + init_data.serialized_state.len() + ); + + if std::mem::discriminant(&fsm) != std::mem::discriminant(&FiniteStateMachine::Booted) { + let _ = ack.send(Err(anyhow::anyhow!("init received while not in Booted"))); + log::error!("received init command while not in a booted state: ignored"); + } else { + // Add init logic here + eprintln!("INIT_HOOK hello from inside init"); + + // IMPORTANT: don't log secret values in production; log presence only. + match std::env::var("DEMO_SECRET") { + Ok(value) => eprintln!("DEMO_SECRET is: {}", value), + Err(e) => eprintln!("Couldn't read DEMO_SECRET: {}", e), + } + + fsm = FiniteStateMachine::Initialized; + + eprintln!("INIT_HOOK done -> state Initialized"); + let _ = ack.send(Ok(())); + } + } + ContainerFunctionRequest::Cast(event) => { + log::info!("cast, src {}, msg {} bytes", event.src, event.msg.len()); + if std::mem::discriminant(&fsm) != std::mem::discriminant(&FiniteStateMachine::Initialized) { + log::error!("received cast command while not in an initialized state: ignored"); + } else { + // Add cast logic here. + log::info!("CAST_HOOK hello from inside cast"); + + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + if let Some(ref mut host_client_api) = host_client_api { + match host_client_api + .cast(edgeless_api::guest_api_host::OutputEventData { + originator: instance_id.unwrap(), + alias: "output".to_string(), + msg: event.msg, + }) + .await + { + Err(err) => { + fsm = FiniteStateMachine::Error; + log::error!("error when casting an event to alias \"output\": {}", err); + } + _ => { + log::info!("event recasted successfully to \"output\""); + } + } + } + } + } + ContainerFunctionRequest::Call(event, reply_sender) => { + log::info!("call, src {}, msg {} bytes", event.src, event.msg.len()); + let res = match std::mem::discriminant(&fsm) == std::mem::discriminant(&FiniteStateMachine::Initialized) { + false => { + log::error!("received call command while not in an initialized state: ignored"); + edgeless_api::guest_api_function::CallReturn::Err + } + true => { + // Add call logic here. + edgeless_api::guest_api_function::CallReturn::Reply(event.msg) + } + }; + match reply_sender.send(Ok(res)) { + Ok(_) => {} + Err(err) => { + fsm = FiniteStateMachine::Error; + log::error!("internal communication error on call command: {:?}", err); + } + } + } + ContainerFunctionRequest::Stop() => { + log::info!("stop"); + if std::mem::discriminant(&fsm) != std::mem::discriminant(&FiniteStateMachine::Initialized) { + log::error!("received stop command while not in an initialized state: ignored"); + } else { + // Add stop logic here. + fsm = FiniteStateMachine::Stopped; + } + } + } + } + } + + pub fn get_api_client(&mut self) -> Box { + Box::new(ContainerFunctionClient { + container_function_client: Box::new(GuestAPIFunctionClient { sender: self.sender.clone() }), + }) + } +} + +pub struct ContainerFunctionClient { + container_function_client: Box, +} + +impl edgeless_api::outer::container_function::ContainerFunctionAPI for ContainerFunctionClient { + fn guest_api_function(&mut self) -> Box { + self.container_function_client.clone() + } +} + +#[derive(Clone)] +pub struct GuestAPIFunctionClient { + sender: futures::channel::mpsc::UnboundedSender, +} + +#[async_trait::async_trait] +impl edgeless_api::guest_api_function::GuestAPIFunction for GuestAPIFunctionClient { + async fn boot(&mut self, boot_data: edgeless_api::guest_api_function::BootData) -> anyhow::Result<()> { + let (ack_tx, ack_rx) = tokio::sync::oneshot::channel::>(); + + self.sender + .send(ContainerFunctionRequest::Boot(boot_data.clone(), ack_tx)) + .await + .map_err(|err| anyhow::anyhow!("GuestAPIFunction::boot channel error: {}", err))?; + + ack_rx + .await + .map_err(|err| anyhow::anyhow!("GuestAPIFunction::boot ack dropped: {}", err))? + } + + async fn init(&mut self, init_data: edgeless_api::guest_api_function::FunctionInstanceInit) -> anyhow::Result<()> { + let (ack_tx, ack_rx) = tokio::sync::oneshot::channel::>(); + + self.sender + .send(ContainerFunctionRequest::Init(init_data.clone(), ack_tx)) + .await + .map_err(|err| anyhow::anyhow!("GuestAPIFunction::init channel error: {}", err))?; + + ack_rx + .await + .map_err(|err| anyhow::anyhow!("GuestAPIFunction::init ack dropped: {}", err))? + } + + async fn cast(&mut self, event: edgeless_api::guest_api_function::InputEventData) -> anyhow::Result<()> { + match self.sender.send(ContainerFunctionRequest::Cast(event.clone())).await { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::anyhow!("GuestAPIFunction::cast channel error: {}", err)), + } + } + + async fn call( + &mut self, + event: edgeless_api::guest_api_function::InputEventData, + ) -> anyhow::Result { + let (reply_sender, reply_receiver) = + tokio::sync::oneshot::channel::>(); + match self.sender.send(ContainerFunctionRequest::Call(event.clone(), reply_sender)).await { + Ok(_) => match reply_receiver.await { + Ok(ret) => ret, + Err(err) => Err(anyhow::anyhow!("GuestAPIFunction::call error: {}", err)), + }, + Err(err) => Err(anyhow::anyhow!("GuestAPIFunction::call channel error: {}", err)), + } + } + + async fn stop(&mut self) -> anyhow::Result<()> { + match self.sender.send(ContainerFunctionRequest::Stop()).await { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::anyhow!("GuestAPIFunction::stop channel error: {}", err)), + } + } +} + diff --git a/examples/sgx_container/policy.yaml b/examples/sgx_container/policy.yaml new file mode 100644 index 00000000..4bcf4dc1 --- /dev/null +++ b/examples/sgx_container/policy.yaml @@ -0,0 +1,39 @@ +name: policy +version: "0.3.10" +#predecessor: "6bc7726822ae70d66b13673f54402b22d0b6d6d4062bf1bb4c4eb622e7a537ef" + +security: + attestation: + tolerate: + - "insecure-configuration" + - "outdated-tcb" + ignore_advisories: + - "INTEL-SA-00289" + - "INTEL-SA-00334" + - "INTEL-SA-00614" + - "INTEL-SA-00615" + - "INTEL-SA-00617" + - "INTEL-SA-00828" + +# A service in a SCONE session is a SCONE program with a specific software state and configuration +services: + - name: edgeless_function + #the command to be executed; match the CMD in function's Dockerfile + command: ["edgeless_container_function", "--endpoint", "http://0.0.0.0:7101"] + #the process working directory from which path resolution of relative paths starts + pwd: / + attestation: + mrenclave: + - b59a1afc14c69927bcd6847f033bff976e10e48c0a5c1b860576c70cd8ede1b1 + environment: + #Values that are needed to provide the expected service + #Pre-existing variables are not trusted, only those in the policy will be available to SCONE program + DEMO_SECRET: "$$SCONE::enclave_secret$$" + #used to inject the secret into the service's environment + SCONE_ALLOW_DLOPEN: "2" + SCONE_LOG: "DEBUG" + +secrets: + - name: enclave_secret + kind: ascii + value: "hello, EDGELESS enclave" diff --git a/examples/sgx_container/provision-cas.sh b/examples/sgx_container/provision-cas.sh new file mode 100644 index 00000000..85ea5670 --- /dev/null +++ b/examples/sgx_container/provision-cas.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/dsd.sh" +source "$SCRIPT_DIR/sgx-groups.sh" +sgx_detect_groups + +if [[ -z "${SGX_ENCLAVE_DEV:-}" ]]; then + echo "Error: No SGX enclave device found. HW mode required." + exit 1 +fi + +# --- CONFIG --- +CAS_ADDR="${CAS_ADDR:-cas:8081}" +CAS_PROVISIONING_TOKEN="${CAS_PROVISIONING_TOKEN:?set CAS_PROVISIONING_TOKEN}" +CAS_KEY_HASH="${CAS_KEY_HASH:?set CAS_KEY_HASH}" + +echo "Using SGX enclave device: $SGX_ENCLAVE_DEV" +[[ -n "${SGX_PROVISION_DEV:-}" ]] && echo "Using SGX provision device: $SGX_PROVISION_DEV" +echo "Using CAS_ADDR: $CAS_ADDR" +echo "Using LAS via Docker DNS: las:18766" +[[ -n "${SGX_GID:-}" ]] && echo "Host sgx GID: ${SGX_GID}" +[[ -n "${SGX_PRV_GID:-}" ]] && echo "Host sgx_prv GID: ${SGX_PRV_GID}" + +# Ask user about attestation +read -r -p "Do you want to attest CAS during provisioning? (y/n): " ATTEST +ATTEST="${ATTEST,,}" # lowercase + +PROVISION_MODE_ARGS=() +if [[ "$ATTEST" == "y" || "$ATTEST" == "yes" ]]; then + if [[ -z "${CAS_MRENCLAVE:-}" ]]; then + echo "CAS_MRENCLAVE is not set." + echo "Use: get_mrenclave.sh and export to the variable CAS_MRENCLAVE. Then exit." + exit 1 + fi + PROVISION_MODE_ARGS=(with-attestation --mrenclave "$CAS_MRENCLAVE") +else + PROVISION_MODE_ARGS=(only_for_testing-without-attestation) +fi + +# Build group-add args (may be empty) +GROUP_ARGS="$(sgx_group_add_docker_args || true)" + +# shellcheck disable=SC2086 +docker run --rm -it \ + --network scone-net \ + -v "$PWD:$PWD" -w "$PWD" \ + --device "$SGX_ENCLAVE_DEV" \ + ${SGX_PROVISION_DEV:+--device "$SGX_PROVISION_DEV"} \ + ${GROUP_ARGS} \ + -e SCONE_MODE=HW \ + -e SCONE_LAS_ADDR=las:18766 \ + registry.scontain.com/sconecuratedimages/crosscompilers:ubuntu24.04-scone6.0.5 \ + scone cas provision \ + --token "$CAS_PROVISIONING_TOKEN" \ + -c "$CAS_KEY_HASH" \ + --config-file ./cas/cas-default-owner-config.toml \ + "$CAS_ADDR" \ + "${PROVISION_MODE_ARGS[@]}" + +echo "CAS provisioning complete." + diff --git a/examples/sgx_container/reset-cas.sh b/examples/sgx_container/reset-cas.sh new file mode 100644 index 00000000..5f75f801 --- /dev/null +++ b/examples/sgx_container/reset-cas.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +set -euo pipefail + +# Reset SCONE CAS containers + CAS data volume + generated local files for this repo. +# This is destructive: it removes CAS state (DB/keys), containers, and local artifacts. + +CAS_CONTAINER="${CAS_CONTAINER:-cas}" +CAS_INIT_CONTAINER="${CAS_INIT_CONTAINER:-cas-init}" + +# Your repo shows the volume name as sgx_container_cas-data (compose-style). +CAS_VOLUME="${CAS_VOLUME:-sgx_container_cas-data}" + +# Local artifacts in the current directory you want removed +FILES_TO_REMOVE=( + "cas" + "cas.mrenclave.out" + "docker-cas-compose.yml" +) + +echo "Stopping/removing containers (ignore if missing): $CAS_CONTAINER $CAS_INIT_CONTAINER" +docker rm -f "$CAS_CONTAINER" >/dev/null 2>&1 || true +docker rm -f "$CAS_INIT_CONTAINER" >/dev/null 2>&1 || true + +echo "Removing CAS data volume (ignore if missing): $CAS_VOLUME" +docker volume rm "$CAS_VOLUME" >/dev/null 2>&1 || true + +echo "Removing local artifacts:" +for f in "${FILES_TO_REMOVE[@]}"; do + if [[ -e "$f" ]]; then + rm -rf -- "$f" + echo " removed: $f" + else + echo " missing: $f" + fi +done + +echo "Done." diff --git a/examples/sgx_container/scone-list-registry-tags.sh b/examples/sgx_container/scone-list-registry-tags.sh new file mode 100644 index 00000000..9315061e --- /dev/null +++ b/examples/sgx_container/scone-list-registry-tags.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +# +# How to use: +# export SCONE_HUB_ACCESS_TOKEN= +# export REPO= # e.g. "cli", "services", "crosscompilers" +# ./list-images.sh +# +# This script: +# 1) Checks required environment variables +# 2) Finds the GitLab project ID for the given repo +# 3) Lists available Docker image tags in the registry +# + +set -euo pipefail + +# 1) Check required environment variables +if [[ -z "${SCONE_HUB_ACCESS_TOKEN:-}" ]]; then + echo "Error: SCONE_HUB_ACCESS_TOKEN is not set" + exit 1 +fi + +if [[ -z "${REPO:-}" ]]; then + echo "Error: REPO is not set" + exit 1 +fi + +# 2) Query GitLab for the project ID +PROJECT_ID=$( + curl -s \ + -H "PRIVATE-TOKEN: $SCONE_HUB_ACCESS_TOKEN" \ + "https://gitlab.scontain.com/api/v4/projects?search=$REPO" \ + | jq -r '.[0].id' +) + +if [[ -z "$PROJECT_ID" || "$PROJECT_ID" == "null" ]]; then + echo "Error: Could not find project ID for repo '$REPO'" + exit 1 +fi + +export PROJECT_ID +echo "Found PROJECT_ID=$PROJECT_ID for repo '$REPO'" + +# 3) List image tags in the repository +echo "Available image tags:" +curl -s \ + -H "PRIVATE-TOKEN: $SCONE_HUB_ACCESS_TOKEN" \ + "https://gitlab.scontain.com/api/v4/projects/$PROJECT_ID/registry/repositories?tags=1" \ +| jq -r '.[].tags[].name' diff --git a/examples/sgx_container/setup-and-run-pccs.sh b/examples/sgx_container/setup-and-run-pccs.sh new file mode 100644 index 00000000..e0180d19 --- /dev/null +++ b/examples/sgx_container/setup-and-run-pccs.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +# Usage: +# # Start PCCS (requires Intel PCS API key): +# INTEL_API_KEY="YOUR_INTEL_PCS_API_KEY" ./setup-and-run-pccs.sh +# +# # Start PCCS and also register this machine's platform using PCKIDRetrievalTool: +# INTEL_API_KEY="YOUR_INTEL_PCS_API_KEY" REGISTER_PLATFORM=true ./setup-and-run-pccs.sh +# +# Notes: +# - The script prints (and also writes to ./pccs-tokens.env) the raw ADMIN_TOKEN and USER_TOKEN. +# - Use USER_TOKEN with PCKIDRetrievalTool and ADMIN_TOKEN for PCCS admin API calls. +# - PCCS runs on https://localhost:8081 and typically uses a self-signed cert (curl uses -k). + +set -euo pipefail + +# 1) Check required environment variable +if [[ -z "${INTEL_API_KEY:-}" ]]; then + echo "Error: INTEL_API_KEY is not set" + exit 1 +fi + +# 2) Generate random tokens (raw passwords) +ADMIN_TOKEN=$(openssl rand -hex 16) +USER_TOKEN=$(openssl rand -hex 16) + +# 3) Generate SHA-512 hashes for the PCCS config (PCCS expects hashes, not raw tokens) +ADMIN_HASH=$(echo -n "$ADMIN_TOKEN" | sha512sum | awk '{print $1}') +USER_HASH=$(echo -n "$USER_TOKEN" | sha512sum | awk '{print $1}') + +PCCS_IMAGE="registry.scontain.com/sconecuratedimages/services:pccs" +PCCS_CONTAINER_NAME="pccs" +PCCS_PORT=8081 +PCCS_DATA_VOLUME="pccs-data" +SCONE_NETWORK="scone-net" + +echo "Pulling PCCS image: $PCCS_IMAGE" +docker pull "$PCCS_IMAGE" + +# 4) Run PCCS container +echo "Starting PCCS on port $PCCS_PORT..." +docker run -d \ + --name "$PCCS_CONTAINER_NAME" \ + --network "$SCONE_NETWORK" \ + -p "${PCCS_PORT}:${PCCS_PORT}" \ + -e APIKEY="$INTEL_API_KEY" \ + -e PCCS_PORT="$PCCS_PORT" \ + -e ADMIN_TOKEN_HASH="$ADMIN_HASH" \ + -e USER_TOKEN_HASH="$USER_HASH" \ + -e PCCS_LOG_LEVEL=debug \ + -v "${PCCS_DATA_VOLUME}:/var/lib/pccs" \ + "$PCCS_IMAGE" >/dev/null + +# Persist tokens for automation +umask 077 +TOKENS_FILE="${TOKENS_FILE:-./pccs-tokens.env}" +cat > "$TOKENS_FILE" </dev/null + +echo "Restarting PCCS to pick up config changes..." +docker restart "$PCCS_CONTAINER_NAME" >/dev/null + +# Auth readiness (admin token) - only after patch+restart +echo "Verifying PCCS auth" +auth_ok=false +last_code="" +for i in {1..30}; do + last_code="$(curl -k -s -o /dev/null -w "%{http_code}" \ + -H "admin-token: ${ADMIN_TOKEN}" \ + "https://localhost:${PCCS_PORT}/sgx/certification/v4/platforms" || true)" + + if [[ "$last_code" != "401" && "$last_code" != "000" ]]; then + auth_ok=true + break + fi + sleep 1 +done + +if [[ "$auth_ok" != "true" ]]; then + echo "ERROR: PCCS auth check failed (last HTTP code: ${last_code:-})" + echo "Dumping configured hashes in container:" + docker exec -it "$PCCS_CONTAINER_NAME" sh -lc 'grep -nE "ApiKey|UserTokenHash|AdminTokenHash" /opt/intel/sgx-dcap-pccs/config/default.json' || true + docker logs --tail 200 "$PCCS_CONTAINER_NAME" || true + exit 1 +fi + +echo "---------------------------------------------------" +echo "PCCS is running with Authentication Enabled" +echo "---------------------------------------------------" +echo "Admin Token (Raw): $ADMIN_TOKEN" +echo "User Token (Raw): $USER_TOKEN" +echo "Tokens saved to: $TOKENS_FILE" +echo "---------------------------------------------------" +echo "Test Admin Access with:" +echo "curl -k -H \"admin-token: $ADMIN_TOKEN\" https://localhost:${PCCS_PORT}/sgx/certification/v4/platforms" + +# 5) Optionally register this platform in PCCS (recommended for local dev) +if [[ "${REGISTER_PLATFORM:-false}" == "true" ]]; then + if ! command -v PCKIDRetrievalTool >/dev/null 2>&1; then + echo "REGISTER_PLATFORM=true but PCKIDRetrievalTool not found in PATH." + echo "Install it first or add it to PATH." + exit 1 + fi + + echo "Registering platform with PCCS (this host)..." + + # Avoid sudo creating an unreadable /tmp file: + OUT="${OUT:-$HOME/pckid_retrieval.csv}" + + sudo PCKIDRetrievalTool \ + -f "$OUT" \ + -url "https://localhost:${PCCS_PORT}" \ + -user_token "$USER_TOKEN" \ + -use_secure_cert false + + # Ensure current user can read it + sudo chown "${SUDO_USER:-$USER}:${SUDO_USER:-$USER}" "$OUT" 2>/dev/null || true + sudo chmod 0644 "$OUT" 2>/dev/null || true + + echo "Platform registration attempt complete. Output: $OUT" + head -n 5 "$OUT" || true +fi diff --git a/examples/sgx_container/setup-cas.sh b/examples/sgx_container/setup-cas.sh new file mode 100644 index 00000000..0243f1b0 --- /dev/null +++ b/examples/sgx_container/setup-cas.sh @@ -0,0 +1,187 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +# +# Script to set up SCONE CAS locally: +# - pulls CAS image +# - creates ./cas directory +# - writes cas/cas-default-owner-config.toml and cas/cas.toml +# - sources dsd.sh to detect SGX devices +# - generates docker-cas-compose.yml (with cas-init) +# - computes & saves CAS hash output (SCONE_HASH=1) +# - mounts sgx_default_qcnl.conf if present next to this script +# - detects host GIDs for sgx + sgx_prv and adds group_add with numeric IDs +# +# IMPORTANT: +# source ./setup-cas.sh +# SGX_SIM_MODE=1 source ./setup-cas.sh + +set -euo pipefail +export CAS="registry.scontain.com/sconecuratedimages/services:cas" +#export CAS="registry.scontain.com/sconecuratedimages/services:cas.preprovisioned.scone.cloud" +#docker pull "$CAS" + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CAS_DIR="$SCRIPT_DIR/cas" + +mkdir -p "$CAS_DIR" + +# Avoid root-owned ./cas trap +if [[ ! -w "$CAS_DIR" ]]; then + echo "ERROR: '$CAS_DIR' is not writable by $(id -un)." + echo "Fix: sudo chown -R $USER:$USER '$CAS_DIR'" + exit 1 +fi + +# --- Save CAS hash output --- +HASH_OUT="$SCRIPT_DIR/cas.mrenclave.out" +echo "Computing CAS enclave measurement (SCONE_HASH=1) ..." +docker run --rm -e SCONE_HASH=1 "$CAS" 2>&1 | tee "$HASH_OUT" >/dev/null +echo "Wrote: $HASH_OUT" + +# Create CAS default owner config +cat > "$CAS_DIR/cas-default-owner-config.toml" <<'EOF' +[ias] +spid = "00000000000000000000000000000000" +linkable_quotes = true +sp_key = "00000000000000000000000000000000" + +[dcap] +subscription_key = "00000000000000000000000000000000" +EOF + +# Create CAS service config +cat > "$CAS_DIR/cas.toml" <<'EOF' +[api] +api_listen = "0.0.0.0:8081" +enclave_listen = "0.0.0.0:18765" + +[database] +path = "/var/lib/cas/cas.db" +EOF + +# Detect devices via dsd.sh +source "$SCRIPT_DIR/dsd.sh" + +# Detect SGX group IDs on the host (numeric) +source "$SCRIPT_DIR/sgx-groups.sh" +sgx_detect_groups + +# SIM vs HW +SCONE_MODE="HW" +DEVICES_YAML="" + +if [[ -z "${SGX_ENCLAVE_DEV:-}" || "${SGX_SIM_MODE:-0}" == "1" ]]; then + SCONE_MODE="SIM" + echo "Configuring CAS for SIMULATION mode." +else + DEVICES_YAML=" devices: + - \"${SGX_ENCLAVE_DEV}:${SGX_ENCLAVE_DEV}\"" + if [[ -n "${SGX_PROVISION_DEV:-}" ]]; then + DEVICES_YAML="${DEVICES_YAML} + - \"${SGX_PROVISION_DEV}:${SGX_PROVISION_DEV}\"" + fi +fi + +# group_add snippet only makes sense in HW mode +GROUP_ADD_YAML="" +if [[ "${SCONE_MODE}" == "HW" ]]; then + GROUP_ADD_YAML="$(sgx_group_add_yaml || true)" + if [[ -z "${GROUP_ADD_YAML}" ]]; then + echo "WARN: Could not detect host groups 'sgx'/'sgx_prv'. CAS may not access SGX devices unless it runs as root." + fi +fi + +# Mount sgx_default_qcnl.conf if present +QCNL_VOL_LINE="" +if [[ -f "$SCRIPT_DIR/sgx_default_qcnl.conf" ]]; then + QCNL_VOL_LINE=" - \"./sgx_default_qcnl.conf:/etc/sgx_default_qcnl.conf:ro\"" + echo "Found $SCRIPT_DIR/sgx_default_qcnl.conf -> will mount into CAS." +else + echo "No $SCRIPT_DIR/sgx_default_qcnl.conf found -> not mounting QCNL config." +fi + +# CAS image runs as uid=1000/gid=1000 (you verified this) +CAS_UID="1000" +CAS_GID="1000" + +# Generate docker-compose for CAS (with cas-init) +cat > "$SCRIPT_DIR/docker-cas-compose.yml" < + sh -c "mkdir -p /var/lib/cas && + chown -R ${CAS_UID}:${CAS_GID} /var/lib/cas && + chmod -R u+rwX /var/lib/cas" + restart: "no" + + cas: + image: $CAS + container_name: cas + depends_on: + - cas-init + networks: + - scone-net + command: cas -c /etc/cas/cas.toml + environment: + - SCONE_LOG=info + - SCONE_MODE=${SCONE_MODE} + - SCONE_LAS_ADDR=las:18766 + privileged: true +EOF + +# Append group_add stanza (if available) +if [[ -n "${GROUP_ADD_YAML}" ]]; then + cat >> "$SCRIPT_DIR/docker-cas-compose.yml" <> "$SCRIPT_DIR/docker-cas-compose.yml" <> "$SCRIPT_DIR/docker-cas-compose.yml" </dev/null 2>&1; } + +as_root +need_cmd curl +need_cmd tar + +echo "[1/6] Base deps" +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -y --no-install-recommends ca-certificates curl gnupg tar gzip findutils coreutils + +mkdir -p "$WORKDIR" +cd "$WORKDIR" + +download() { + local url="$1" out="$2" + if [[ -f "$out" ]]; then + echo " - Already have $out" + else + echo " - Downloading $url" + curl -fsSLo "$out" "$url" + fi +} + +echo "[2/6] Remove old SGX local repo list(s)" +rm -f /etc/apt/sources.list.d/sgx_local_repo.list \ + /etc/apt/sources.list.d/sgx_debian_local_repo.list \ + /etc/apt/sources.list.d/*sgx*.list || true + +echo "[3/6] Download + extract Intel SGX local repo into $INTEL_OPT" +download "$DEB12_DIR/$LOCAL_REPO_TGZ" "$LOCAL_REPO_TGZ" + +mkdir -p "$INTEL_OPT" +rm -rf "$REPO_DIR" +tar xzf "$LOCAL_REPO_TGZ" -C "$INTEL_OPT" +chown -R root:root "$REPO_DIR" +chmod -R a+rX "$REPO_DIR" + +# Sanity: ensure Release exists +if [[ ! -f "$REPO_DIR/dists/bookworm/Release" ]]; then + echo "ERROR: $REPO_DIR/dists/bookworm/Release not found. Repo extract looks wrong." + echo "Contents:" + ls -la "$REPO_DIR" || true + exit 1 +fi + +echo "[4/6] Add APT key + file:// repo source" +mkdir -p "$KEYRING_DIR" +cp -f "$REPO_DIR/keys/intel-sgx.key" "$KEYRING_FILE" +chmod 0644 "$KEYRING_FILE" + +cat > "$APT_LIST" <&2 +fi + +# Create docker-compose file +cat > "$SCRIPT_DIR/docker-las-compose.yml" <> "$SCRIPT_DIR/docker-las-compose.yml" <> "$SCRIPT_DIR/docker-las-compose.yml" <<'EOF' + restart: on-failure + ports: + - "18766:18766" + +networks: + scone-net: + external: true +EOF + +echo "Wrote: $SCRIPT_DIR/docker-las-compose.yml" +echo "AESM socket mounted: $AESM_SOCKET" diff --git a/examples/sgx_container/sgx-groups.sh b/examples/sgx_container/sgx-groups.sh new file mode 100644 index 00000000..2b9c06ac --- /dev/null +++ b/examples/sgx_container/sgx-groups.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-FileCopyrightText: © 2026 Technical University of Crete +# SPDX-License-Identifier: MIT + +# sgx-groups.sh +# +# WHY THIS IS USED: +# SGX device nodes (/dev/sgx_enclave, /dev/sgx_provision) are usually 0660 and owned by +# root:sgx / root:sgx_prv. Non-root containers need those group IDs to access SGX HW. +# Group names often don't exist inside containers, so we detect HOST numeric GIDs for +# sgx/sgx_prv and use them in compose `group_add` or `docker run --group-add`. + +set -euo pipefail + +sgx__get_gid() { + local grp="$1" + local line gid + line="$(getent group "$grp" 2>/dev/null || true)" + [[ -z "$line" ]] && return 1 + gid="$(echo "$line" | cut -d: -f3)" + [[ -n "$gid" ]] || return 1 + echo "$gid" +} + +sgx_detect_groups() { + SGX_GID="" + SGX_PRV_GID="" + + SGX_GID="$(sgx__get_gid sgx || true)" + SGX_PRV_GID="$(sgx__get_gid sgx_prv || true)" + + export SGX_GID SGX_PRV_GID +} + +sgx_group_add_yaml() { + local out="" + [[ -n "${SGX_GID:-}" ]] || [[ -n "${SGX_PRV_GID:-}" ]] || return 0 + + out=" group_add:" + [[ -n "${SGX_GID:-}" ]] && out="${out} + - \"${SGX_GID}\" # sgx" + [[ -n "${SGX_PRV_GID:-}" ]] && out="${out} + - \"${SGX_PRV_GID}\" # sgx_prv" + + echo "$out" +} + +sgx_group_add_docker_args() { + local args=() + [[ -n "${SGX_GID:-}" ]] && args+=(--group-add "${SGX_GID}") + [[ -n "${SGX_PRV_GID:-}" ]] && args+=(--group-add "${SGX_PRV_GID}") + printf '%s ' "${args[@]:-}" +} diff --git a/examples/sgx_container/sgx_default_qcnl.conf b/examples/sgx_container/sgx_default_qcnl.conf new file mode 100644 index 00000000..b5e609f0 --- /dev/null +++ b/examples/sgx_container/sgx_default_qcnl.conf @@ -0,0 +1,4 @@ +{ + "pccs_url": "https://pccs:8081/sgx/certification/v4/", + "use_secure_cert": true +} diff --git a/examples/sgx_container/workflow.json b/examples/sgx_container/workflow.json new file mode 100644 index 00000000..e44ab6c4 --- /dev/null +++ b/examples/sgx_container/workflow.json @@ -0,0 +1,20 @@ +{ + "functions": [ + { + "name": "sgx", + "class_specification": { + "id": "sgx", + "function_type": "CONTAINER", + "version": "0.1", + "code": "container:edgeless-sgx-function-rust:dev", + "outputs": [] + }, + "output_mapping": {}, + "annotations": { + "init-payload": "nothing interesting" + } + } + ], + "resources": [], + "annotations": {} +} From b4f34af1387cc304bbd756110772f9a7cdf37dce Mon Sep 17 00:00:00 2001 From: Nikos Alexiou Date: Sun, 22 Feb 2026 23:08:20 +0200 Subject: [PATCH 2/3] Added SGX container example --- examples/sgx_container/README.md | 89 +++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/examples/sgx_container/README.md b/examples/sgx_container/README.md index 3dfda061..17dd458d 100644 --- a/examples/sgx_container/README.md +++ b/examples/sgx_container/README.md @@ -8,6 +8,7 @@ This example demonstrates how to run **EDGELESS** functions inside It provides a minimal, reproducible setup that: - Builds a **Rust-based function** as a container image - Cross-compiles the function with **SCONE** +- Includes scripts to setup a local PCCS/DCAP service, SCONE's LAS and CAS - Includes a workflow containing a **single function** --- @@ -28,7 +29,7 @@ You must build EDGELESS locally and configure it appropriately: ### 2. SCONE Infrastructure Access -Access to the SCONE container registry is required in order to pull images. +Access to the SCONE container registry is required in order to pull the appropriate images. 1. Register at 2. Create a **Personal Access Token** with appropriate permissions @@ -60,24 +61,96 @@ Folder layout: └── sgx_container/ ├── Dockerfile <- Dockerfile lives here ├── build.sh - └── workflow.json + ├── workflow.json + ├── ... + └── modified_files <- files that were modified to run the example ``` +### About modified files: +- `container_devices.rs` goes to `edgeless_node/src/container_runner/` in order to choose the in-tree SGX driver +- `container_function.rs`: implements the function logic, see folder structure above. ___ -## Build and run +## Building the function image +To build the image, first generate an enclave signing private RSA key for scone-signer: + +```bash +./generate-enclave-signing-key.sh +``` +Then use the provided build script: ```bash -user@host:~/edgeless$ examples/sgx_container/build.sh -user@host:~/edgeless$ target/debug/edgeless_cli workflow start examples/sgx_container/workflow.json +./build.sh ``` +___ +## Setup SGX Provisioning Certificate Caching Service (PCCS) + +Use the provided script to start a PCCS Docker container, generate and save admin/user auth tokens, patch the PCCS config with your Intel API key and token hashes, verify the service is reachable, and, optionally register the host platform with PCKIDRetrievalTool. +```bash +./setup-and-run-pccs.sh +``` + --- +## Setup and run SCONE's Local Attestation Service (LAS) + +LAS runs on the node. It helps SGX apps create attestation quotes and connects them to the attestation infrastructure (like PCCS), so the enclave can prove it’s genuine and running trusted code. + +```bash +./setup-las.sh +docker compose -f docker-las-compose.yml up -d +``` +___ + +## Setup and run SCONE's Configuration and Attestation Service (CAS) +The role of CAS is to verify enclave attestation and securely deliver the function’s secrets only to trusted enclaves. + +### CAS setup and run + +```bash +./setup-cas.sh +docker compose -f docker-cas-compose.yml up -d +``` + +This script sets up CAS locally by preparing the CAS config files, detecting SGX devices and host SGX-related group IDs, generating the Docker Compose setup (including cas-init), computing and saving the CAS hash (SCONE_HASH=1), and mounting sgx_default_qcnl.conf if it exists next to the script. + +### SCONE CAS provisioning + +CAS must first be provisioned, meaning it is initialized as a trusted service (with its own identity/keys) so it can securely manage secrets and attestation decisions. + +```bash +./provision-cas.sh +``` + +The script provisions CAS in HW mode by detecting SGX devices/groups, asks whether to do attested provisioning, and then provisions cas in a Docker container using LAS and your provisioning token/key hash (optionally verifying CAS via CAS_MRENCLAVE). +___ + +### Policy upload to CAS + +After provisioning, CAS is ready to accept policies (i.e. security configuration and rules for apps). CAS uses those policies to decide which enclaves are allowed to receive secrets based on enclave identity measurements like: + +- MRENCLAVE: the exact enclave code hash (specific build) +- MRSIGNER: the signer identity (who signed the enclave), allowing trust across compatible versions signed by the same key + + +```bash +./attest-cas-upload_policy.sh +``` + +This script attests CAS (optionally pinned to a specific MRENCLAVE), uploads a policy/session file to it, and finally verifies that the policy was stored successfully. + +A dummy `policy.yml` file is provided. Also, in case you want to completely remove CAS by deleting the CAS container, remove the CAS data volume, and clean the generated local CAS files/artifacts, use: + +```bash +./reset-cas.sh +``` +___ + + ## Notes 1. SCONE cross-compilation currently uses an **older EDGELESS commit** that does **not** depend on `aws-lc-sys`. Newer versions introduce a dependency on `aws-lc-sys`, a low-level -crate wrapping C and assembly code, which prevents successful cross-compilation. This does not refer to the local copy of your EDGELESS but to the commit brought in the build stage. +crate wrapping C and assembly code, which prevents successful cross-compilation. This does not refer to the local copy of your EDGELESS but to the commit brought in the build stage. -2. A one-stage Dockerfile variant is provided -with the relevant workflow & build script- for testing purposes eg measure function instantiation times when images are much larger. The default two-stage build achieves up to **10× smaller images**, typically reducing image sizes from a few GB to hundreds of MB. +2. The image name *must* contain `edgeless-sgx-function- ` otherwise EDGELESS will not pass the sgx driver to docker. See [docker_utils.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/docker_utils.rs) and [container_devices.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/container_devices.rs) -3. The image name *must* contain `edgeless-sgx-function- ` otherwise EDGELESS will not pass the sgx driver to docker. Go to [docker_utils.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/docker_utils.rs) and [container_devices.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/container_devices.rs) From 825ec1a45e0089ad8c9f6e755ca6f0137432228d Mon Sep 17 00:00:00 2001 From: Nikos Alexiou Date: Mon, 23 Feb 2026 11:33:39 +0200 Subject: [PATCH 3/3] Removed changes to main repo's README.md --- README.md | 202 ++++++++++++++++++++++++------------------------------ 1 file changed, 88 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 17dd458d..97c6b598 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,130 @@ -# EDGELESS SGX Container Function Example (SCONE) +![](documentation/edgeless-logo-alpha-200.png) +# EDGELESS Reference Implementation -This example demonstrates how to run **EDGELESS** functions inside -**Intel SGX–protected containers** using **SCONE** and the EDGELESS -**container runtime**. +This repository contains a research prototype of the EDGELESS platform, which is +under active development within the project +[EDGELESS](https://edgeless-project.eu/) (2023-2025). -It provides a minimal, reproducible setup that: -- Builds a **Rust-based function** as a container image -- Cross-compiles the function with **SCONE** -- Includes scripts to setup a local PCCS/DCAP service, SCONE's LAS and CAS -- Includes a workflow containing a **single function** +## Introduction ---- +EDGELESS is a framework that enables +[stateful FaaS agents](documentation/serverless_edge_computing.md) and +is intended especially for edge nodes with limited computational capabilities. -## Prerequisites +An EDGELESS cluster is managed by an ε-CON (controller) and consists of one +or more _orchestration domains_, each managed by an ε-ORC (orchestrator). -To reproduce this example, the following requirements must be met. +The ε-CON allows clients to request the creation of edge services (called +_workflows_), which consist of a collection of interconnected _functions_ +and _resources_ and related annotations. +The management of the lifecycle of any such logical functions/resources is +delegated by the ε-CON to one ε-ORC, which, in turn, manages the lifecycle +of physical function/resource instances on EDGELESS nodes. -### 1. Local EDGELESS Build +Each EDGELESS node may offer multiple run-time environments (e.g., WebAssembly +or Docker) to run function instances and resource providers of different types +(e.g., file logs or Kafka) to create resource instances. -You must build EDGELESS locally and configure it appropriately: +Orchestration in EDGELESS happens at two levels: -- Enable the **container runtime** in `node.toml` -- Set `guest_api_host_url` to an address **reachable from inside containers** - - ❌ Do **not** use `127.0.0.1` or `0.0.0.0` +- _higher level orchestration_ is done by the ε-CON at cluster level + and it maps logical functions/resources to orchestration domains; +- _lower level orchestration_ is done by the ε-ORC within its orchestration + domain, and it maps every logical function/resource to physical instances + on its nodes. ---- +## How to build -### 2. SCONE Infrastructure Access +See [building instructions](BUILDING.md). -Access to the SCONE container registry is required in order to pull the appropriate images. +## Quick start -1. Register at -2. Create a **Personal Access Token** with appropriate permissions -3. Log in to the SCONE registry: +It is recommended that you enable at least info-level log directives with: -```bash -docker login registry.scontain.com -# username: -# password: +```shell +export RUST_LOG=info ``` -___ -## Function Logic -The function logic must be implemented in: +To get the basic system running, first create the default configuration files +(they have fixed hardcoded values): -```text -edgeless/edgeless_container_function/src/container_function.rs +```shell +target/debug/edgeless_inabox -t +target/debug/edgeless_cli -t cli.toml ``` -Folder layout: - -```text -~/edgeless/ -├── edgeless_container_function/ -│ └── src/container_function.rs <- Add your function logic here -├── edgeless_api/ -├── Cargo.toml -├── Cargo.lock -└── examples/ - └── sgx_container/ - ├── Dockerfile <- Dockerfile lives here - ├── build.sh - ├── workflow.json - ├── ... - └── modified_files <- files that were modified to run the example -``` -### About modified files: -- `container_devices.rs` goes to `edgeless_node/src/container_runner/` in order to choose the in-tree SGX driver - -- `container_function.rs`: implements the function logic, see folder structure above. -___ -## Building the function image -To build the image, first generate an enclave signing private RSA key for scone-signer: - -```bash -./generate-enclave-signing-key.sh -``` -Then use the provided build script: -```bash -./build.sh -``` -___ -## Setup SGX Provisioning Certificate Caching Service (PCCS) +which will create: -Use the provided script to start a PCCS Docker container, generate and save admin/user auth tokens, patch the PCCS config with your Intel API key and token hashes, verify the service is reachable, and, optionally register the host platform with PCKIDRetrievalTool. +- `controller.toml` +- `node.toml` +- `orchestrator.toml` +- `cli.toml` +Then you can run the **EDGELESS-in-a-box**, which is a convenience binary that +runs every necessary component as one, using the generated configuration files: -```bash -./setup-and-run-pccs.sh ``` - ---- - -## Setup and run SCONE's Local Attestation Service (LAS) - -LAS runs on the node. It helps SGX apps create attestation quotes and connects them to the attestation infrastructure (like PCCS), so the enclave can prove it’s genuine and running trusted code. - -```bash -./setup-las.sh -docker compose -f docker-las-compose.yml up -d +target/debug/edgeless_inabox ``` -___ -## Setup and run SCONE's Configuration and Attestation Service (CAS) -The role of CAS is to verify enclave attestation and securely deliver the function’s secrets only to trusted enclaves. +Congratulations 🎉 now that you have a complete EDGELESS system you may check +our workflows/function examples, which are representative of the current +EDGELESS features: -### CAS setup and run +- [Example workflows](examples/README.md) +- [Example functions](functions/README.md) -```bash -./setup-cas.sh -docker compose -f docker-cas-compose.yml up -d -``` +## Next steps -This script sets up CAS locally by preparing the CAS config files, detecting SGX devices and host SGX-related group IDs, generating the Docker Compose setup (including cas-init), computing and saving the CAS hash (SCONE_HASH=1), and mounting sgx_default_qcnl.conf if it exists next to the script. +Basics: -### SCONE CAS provisioning +- [Workflows, resources, and functions](documentation/basic_concepts.md) +- [ε-CON](documentation/controller.md) +- [ε-ORC](documentation/orchestrator.md) +- [EDGELESS node](documentation/node.md) +- [EDGELESS command-line clients](documentation/cli.md) +- [A step-by-step example](documentation/deploy_step_by_step.md) -CAS must first be provisioned, meaning it is initialized as a trusted service (with its own identity/keys) so it can securely manage secrets and attestation decisions. +Advanced topics: -```bash -./provision-cas.sh -``` +- [Repository layout](documentation/repository_layout.md) +- [EDGELESS APIs](edgeless_api/README.md) +- [How to create a new function](documentation/rust_functions.md) +- [Local orchestration](documentation/local_orchestration.md) +- [Benchmarking EDGELESS](documentation/benchmark.md) +- [Docker container runtime](documentation/container-runtime.md) +- [A multi-domain example](documentation/example_multidomain.md) +- [Inter-domain workflows](documentation/interdomain_workflows.md) +- [(m)TLS](edgeless_api/src/grpc_impl/tls_certs/README.md) -The script provisions CAS in HW mode by detecting SGX devices/groups, asks whether to do attested provisioning, and then provisions cas in a Docker container using LAS and your provisioning token/key hash (optionally verifying CAS via CAS_MRENCLAVE). -___ +## Known limitations -### Policy upload to CAS +⚠️ There currently are no guarantees on stability, this software is not +(yet) intended for production deployments. -After provisioning, CAS is ready to accept policies (i.e. security configuration and rules for apps). CAS uses those policies to decide which enclaves are allowed to receive secrets based on enclave identity measurements like: +The open issues are tracked on +[GitHub](https://github.com/edgeless-project/edgeless/issues). -- MRENCLAVE: the exact enclave code hash (specific build) -- MRSIGNER: the signer identity (who signed the enclave), allowing trust across compatible versions signed by the same key +Stay tuned (star & watch +[the GitHub project](https://github.com/edgeless-project/edgeless)) to remain +up to date on future developments. +## Contributing -```bash -./attest-cas-upload_policy.sh -``` +We love the open source community of developers ❤️ and we welcome contributions +to EDGELESS. -This script attests CAS (optionally pinned to a specific MRENCLAVE), uploads a policy/session file to it, and finally verifies that the policy was stored successfully. - -A dummy `policy.yml` file is provided. Also, in case you want to completely remove CAS by deleting the CAS container, remove the CAS data volume, and clean the generated local CAS files/artifacts, use: - -```bash -./reset-cas.sh -``` -___ +The [contributing guide](CONTRIBUTING_GUIDE.md) contains some rules you should +adhere to when contributing to this repository. +## License -## Notes -1. SCONE cross-compilation currently uses an **older EDGELESS commit** that does **not** depend on `aws-lc-sys`. Newer versions introduce a dependency on `aws-lc-sys`, a low-level -crate wrapping C and assembly code, which prevents successful cross-compilation. This does not refer to the local copy of your EDGELESS but to the commit brought in the build stage. +The Repository is licensed under the MIT License. Please refer to +[LICENSE](LICENSE) and [CONTRIBUTORS.txt](CONTRIBUTORS.txt). -2. The image name *must* contain `edgeless-sgx-function- ` otherwise EDGELESS will not pass the sgx driver to docker. See [docker_utils.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/docker_utils.rs) and [container_devices.rs](https://github.com/edgeless-project/edgeless/blob/main/edgeless_node/src/container_runner/container_devices.rs) +## Funding +EDGELESS received funding from the [European Health and Digital Executive Agency +(HADEA)](https://hadea.ec.europa.eu/) program under Grant Agreement No +101092950.