Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions examples/sgx_container/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
156 changes: 156 additions & 0 deletions examples/sgx_container/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# 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 scripts to setup a local PCCS/DCAP service, SCONE's LAS and CAS
- 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 the appropriate images.

1. Register at <https://gitlab.scontain.com>
2. Create a **Personal Access Token** with appropriate permissions
3. Log in to the SCONE registry:

```bash
docker login registry.scontain.com
# username: <your username>
# password: <your personal access token>
```
___
## 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
├── ...
└── 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)

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.

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)

74 changes: 74 additions & 0 deletions examples/sgx_container/attest-cas-upload_policy.sh
Original file line number Diff line number Diff line change
@@ -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."
'

34 changes: 34 additions & 0 deletions examples/sgx_container/build-function.sh
Original file line number Diff line number Diff line change
@@ -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."

24 changes: 24 additions & 0 deletions examples/sgx_container/dsd.sh
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading