From 3740eb91e81a84b5f0db503ff66f67532a222c42 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Thu, 25 Dec 2025 22:23:27 +0000 Subject: [PATCH 01/16] WIP: readme revamp and docs restructure --- .agent/WRITING_GUIDE.md | 55 ++ CLAUDE.md | 5 + Cargo.lock | 3 +- README.md | 514 +++-------------- ct_monitor/Cargo.toml | 4 +- ct_monitor/src/main.rs | 47 +- docs/agents.md | 274 +++++++++ .../assets/prelaunch-script.png | Bin .../{security-guide => }/assets/token-env.png | Bin docs/{security-guide => }/cvm-boundaries.md | 2 +- docs/deployment.md | 521 +++++++++--------- docs/inference.md | 112 ++++ docs/onchain-governance.md | 138 +++++ .../security-guide.md => security.md} | 8 +- docs/training.md | 310 +++++++++++ docs/usage.md | 119 ++++ kms/auth-eth/lib/forge-std | 1 + .../lib/openzeppelin-contracts-upgradeable | 1 + .../lib/openzeppelin-foundry-upgrades | 1 + 19 files changed, 1405 insertions(+), 710 deletions(-) create mode 100644 .agent/WRITING_GUIDE.md create mode 100644 docs/agents.md rename docs/{security-guide => }/assets/prelaunch-script.png (100%) rename docs/{security-guide => }/assets/token-env.png (100%) rename docs/{security-guide => }/cvm-boundaries.md (99%) create mode 100644 docs/inference.md create mode 100644 docs/onchain-governance.md rename docs/{security-guide/security-guide.md => security.md} (95%) create mode 100644 docs/training.md create mode 100644 docs/usage.md create mode 160000 kms/auth-eth/lib/forge-std create mode 160000 kms/auth-eth/lib/openzeppelin-contracts-upgradeable create mode 160000 kms/auth-eth/lib/openzeppelin-foundry-upgrades diff --git a/.agent/WRITING_GUIDE.md b/.agent/WRITING_GUIDE.md new file mode 100644 index 00000000..0f4c875b --- /dev/null +++ b/.agent/WRITING_GUIDE.md @@ -0,0 +1,55 @@ +# Documentation Writing Guide + +Guidelines for writing dstack documentation, README, and marketing content. + +## Messaging + +- **Primary keyword**: "Confidential AI" — use in tagline and first paragraph +- **Secondary keyword**: "Private AI" — use in explanatory context +- **Key differentiator**: NVIDIA Confidential Computing support (H100, Blackwell GPUs) +- **Base images**: `dstack-nvidia-0.5.x` for GPU TEE support + +## Writing Style + +- **Don't over-explain** why a framework is needed — assert dstack as the solution, hint at alternatives being insufficient +- **Avoid analogies as taglines** (e.g., "X for Y") — dstack is a new category, not a better version of something else +- **Problem → Solution flow** without explicit labels like "The problem:" or "The solution:" +- **Demonstrate features through actions**, not parenthetical annotations + - Bad: "Generates quotes (enabling *workload identity*)" + - Good: "Generates TDX attestation quotes so users can verify exactly what's running" + +## Architecture Descriptions + +When describing components for security researchers: +- Explain **what each component does** (actions) +- Show **how features emerge** from the architecture naturally +- Map components to security properties: + - Guest Agent → workload identity, key isolation, disk encryption + - KMS → code governance, key derivation bound to attested identity + - Gateway → encrypted networking, RA-TLS + - VMM → Docker Compose native, reproducible OS, GPU allocation + +## Target Audiences + +**Developers** care about: +- Easy onboarding (Docker Compose native) +- No code changes required +- Existing workflow compatibility + +**Security researchers** care about: +- Trust model (what's trusted, what's not) +- How attestation proves code integrity +- How key management prevents operator access +- How code governance is enforced on-chain + +## Feature-to-Component Mapping + +| Feature | Component | How it works | +|---------|-----------|--------------| +| Workload identity | Guest Agent | TDX attestation quotes | +| Isolated keys | Guest Agent + KMS | Per-app key derivation bound to attested identity | +| Encrypted by default | Guest Agent (disk) + Gateway (TLS) | Ephemeral disk keys, RA-TLS | +| Code governance | KMS | On-chain smart contract policies | +| Docker Compose native | VMM | Direct parsing, no translation | +| Reproducible OS | VMM | Deterministic image builds | +| Confidential GPUs | VMM + hardware | NVIDIA H100/Blackwell allocation | diff --git a/CLAUDE.md b/CLAUDE.md index 502564d0..ef533920 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -224,3 +224,8 @@ RPC definitions use `prpc` framework with Protocol Buffers: - Design decisions: `docs/design-and-hardening-decisions.md` When need more detailed info, try to use deepwiki mcp. + +## Agent Resources + +The `.agent/` directory contains AI assistant resources: +- `WRITING_GUIDE.md` — Documentation and README writing guidelines (messaging, style, audiences) diff --git a/Cargo.lock b/Cargo.lock index 348a9493..9b4babe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1731,9 +1731,8 @@ version = "0.5.5" dependencies = [ "anyhow", "clap", - "dstack-gateway-rpc", + "hex", "hex_fmt", - "ra-rpc", "regex", "reqwest", "serde", diff --git a/README.md b/README.md index ac18dd98..5399e39f 100644 --- a/README.md +++ b/README.md @@ -2,499 +2,129 @@ ![dstack](./dstack-logo.svg) -**Deploy containerized apps to TEE with end-to-end security in minutes.** +### The open framework for confidential AI. [![GitHub Stars](https://img.shields.io/github/stars/dstack-tee/dstack?style=flat-square&logo=github)](https://github.com/Dstack-TEE/dstack/stargazers) [![License](https://img.shields.io/github/license/dstack-tee/dstack?style=flat-square)](https://github.com/Dstack-TEE/dstack/blob/master/LICENSE) -[![REUSE status](https://api.reuse.software/badge/github.com/fsfe/reuse-tool)](https://api.reuse.software/info/github.com/fsfe/reuse-tool) -[![Telegram](https://img.shields.io/badge/Community-blue?style=flat-square&logo=telegram&logoColor=fff)](https://t.me/+UO4bS4jflr45YmUx) +[![REUSE status](https://api.reuse.software/badge/github.com/Dstack-TEE/dstack)](https://api.reuse.software/info/github.com/Dstack-TEE/dstack) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Dstack-TEE/dstack) +[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat-square&logo=telegram&logoColor=white)](https://t.me/+UO4bS4jflr45YmUx) -[![Repobeats Analytics](https://repobeats.axiom.co/api/embed/0a001cc3c1f387fae08172a9e116b0ec367b8971.svg "Repobeats analytics image")](https://github.com/Dstack-TEE/dstack/pulse) +[Documentation](https://docs.phala.com/dstack) · [Examples](https://github.com/Dstack-TEE/dstack-examples) · [Community](https://t.me/+UO4bS4jflr45YmUx) --- -## 🚀 Overview +## What is dstack? -dstack is a **developer friendly** and **security first** SDK to simplify the deployment of arbitrary containerized apps into TEE. +dstack is the open framework for confidential AI - deploy AI applications with cryptographic privacy guarantees. -### ✨ Key Features +AI providers ask users to trust them with sensitive data. But trust doesn't scale, and trust can't be verified. With dstack, your containers run inside confidential VMs (Intel TDX) with native support for NVIDIA Confidential Computing (H100, Blackwell). Users can cryptographically verify exactly what's running: private AI with your existing Docker workflow. -- 🔒 **Secure Deployment**: Deploy containerized apps securely in TEE in minutes -- 🛠️ **Familiar Tools**: Use familiar tools - just write a docker-compose.yaml -- 🔑 **Secret Management**: Safely manage secrets and sensitive data -- 📡 **ZT-HTTPS**: Expose services via automated TLS termination +### Features ---- - -## 👥 Community - -dstack is community driven. Open sourced and built by [Kevin Wang](https://github.com/kvinwang) and many others from [Phala Network](https://github.com/Phala-Network), inspired by [Andrew Miller](https://github.com/amiller) (Flashbots & Teleport), and contributed by [Nethermind](https://github.com/NethermindEth/nethermind) and [many others](#contributors). - ---- - -## 📋 Table of Contents - -- [Architecture](#%EF%B8%8F-architecture) -- [Getting Started](#-getting-started) - - [Prerequisites](#prerequisites) - - [Install Dependencies](#install-dependencies) - - [Build and Run](#build-and-run) -- [Usage](#-usage) - - [Deploy an App](#deploy-an-app) - - [Pass Secrets to Apps](#pass-secrets-to-apps) - - [Access the App](#access-the-app) - - [Getting TDX Quote](#getting-tdx-quote-in-docker-container) - - [Container Logs](#container-logs) - - [TLS Passthrough](#tls-passthrough-with-custom-domain) - - [Upgrade an App](#upgrade-an-app) -- [Advanced Features](#-advanced-features) - - [Zero Trust HTTPS](#zero-trust-https) - - [Certificate Transparency Log Monitor](#certificate-transparency-log-monitor) -- [Security](#-security) -- [Troubleshooting](#-troubleshooting) -- [Media Kit](#-media-kit) -- [License](#-license) - ---- - -## 🏗️ Architecture - -
- -Architecture Diagram - -
- -- **`dstack-vmm`**: A service running in a bare TDX host to manage CVMs -- **`dstack-gateway`**: A reverse proxy to forward TLS connections to CVMs -- **`dstack-kms`**: A KMS server to generate keys for CVMs -- **`dstack-guest-agent`**: A service running in CVM to serve containers' key derivation and attestation requests -- **`meta-dstack`**: A Yocto meta layer to build CVM guest images - ---- - -## 🚀 Getting Started - -### Prerequisites - -- A bare metal TDX server setup following [canonical/tdx](https://github.com/canonical/tdx) -- Public IPv4 address assigned to the machine -- At least 16GB RAM, 100GB free disk space -- A domain with DNS access if you want to set up `dstack-gateway` for Zero Trust HTTPS - -> [!NOTE] -> -> Check the [Hardware Requirements](https://docs.phala.network/dstack/hardware-requirements) for more information on buying a bare metal server or renting a server from cloud providers. -> -> If you are looking for a cloud-managed dstack, go to the docs to learn how to [sign up for a Phala Cloud Account](https://docs.phala.network/phala-cloud/getting-started/sign-up-for-cloud-account) and [deploy your first CVM on dstack](https://docs.phala.network/phala-cloud/getting-started/start-from-cloud-ui). - -### Install Dependencies - -```bash -# For Ubuntu 24.04 -sudo apt install -y build-essential chrpath diffstat lz4 wireguard-tools xorriso - -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -### Build and Run - -#### 1. Build the Artifacts - -```bash -git clone https://github.com/Dstack-TEE/meta-dstack.git --recursive -cd meta-dstack/ - -mkdir build -cd build -../build.sh hostcfg -``` - -This outputs the following message: -``` -Config file ../build-config.sh created, please edit it to configure the build -``` - -Review and customize the `build-config.sh` configuration file according to your environment requirements. The file contains network ports, domain settings, and other important parameters. - -```bash -vim ./build-config.sh -``` -Once configured, run the build script again to generate the necessary artifacts: - -```bash -../build.sh hostcfg -``` - -If everything is okay, you should see the built artifacts in the `build` directory: - -```bash -$ ls -certs images dstack-kms kms.toml run dstack-vmm vmm.toml dstack-gateway gateway.toml -``` - -#### 2. Download or Build Guest Image - -**Option A: Download guest image** -```bash -# This will download the guest image from the release page -../build.sh dl 0.5.2 -``` - -**Option B: Build from source** -```bash -# This will build the guest image from source using the yocto meta layer. This will take a while. -../build.sh guest -``` - -> [!IMPORTANT] -> **Note on Reproducible Builds**: The build command above does not guarantee reproducibility. For reproducible builds across different build environments, please refer to the [reproducible build instructions](https://github.com/Dstack-TEE/meta-dstack?tab=readme-ov-file#reproducible-build-the-guest-image) in the meta-dstack repository. - -#### 3. Run Components - -Now you can open 3 terminals to start the components: - -1. **KMS**: `./dstack-kms -c kms.toml` -2. **Gateway**: `sudo ./dstack-gateway -c gateway.toml` -3. **VMM**: `./dstack-vmm -c vmm.toml` - -> [!WARNING] -> This configuration is for local development, and the kms is not secure. You should not use it in production. For production, you should follow the [deployment guide](./docs/deployment.md) and read the [security guide](./docs/security-guide/security-guide.md). - ---- - -## 📱 Usage - -You can manage VMs via the dashboard or [CLI](./docs/vmm-cli-user-guide.md). - -### Deploy an App - -Open the dstack-vmm webpage [http://localhost:9080](http://localhost:9080) (change the port according to your configuration) on your local machine to deploy a `docker-compose.yaml` file: - -
- -VMM Interface - -
- -After the container is deployed, it should take some time to start the CVM and the containers. Time would be vary depending on your workload. - -- **[Logs]**: Click this button to view the CVM logs and monitor container startup progress -- **[Dashboard]**: Once the container is running, click this button to view container information and logs. (Note: This button is only visible when dstack-gateway is enabled. If disabled, you'll need to add a port mapping to port 8090 to access the CVM dashboard) - -
- -Guest Agent Dashboard - -
- -### Pass Secrets to Apps - -When deploying a new App, you can pass private data via Encrypted Environment Variables. These variables can be referenced in the docker-compose.yaml file as shown below: - -
- -Secret Management - -
- -The environment variables will be encrypted in the client-side and decrypted in the CVM before being passed to the containers. - -### Access the App - -Once your app is deployed and listening on an HTTP port, you can access it through dstack-gateway's public domain using these ingress mapping rules: - -- `[-[][s|g]].` → maps to port `` in the CVM - -**Examples**: +**Zero friction onboarding** +- **Docker Compose native**: Bring your docker-compose.yaml as-is. No SDK, no code changes. +- **Encrypted by default**: Network traffic and disk storage encrypted out of the box. -- `3327603e03f5bd1f830812ca4a789277fc31f577-8080.test0.dstack.org` → port `8080` (TLS termination to any TCP) -- `3327603e03f5bd1f830812ca4a789277fc31f577-8080g.test0.dstack.org` → port `8080` (TLS termination with HTTP/2 negotiation) -- `3327603e03f5bd1f830812ca4a789277fc31f577-8080s.test0.dstack.org` → port `8080` (TLS passthrough to any TCP) +**Hardware-rooted security** +- **Private by hardware**: Data encrypted in memory, inaccessible even to the host. +- **Reproducible OS**: Deterministic builds mean anyone can verify the OS image hash. +- **Workload identity**: Every app gets an attested identity users can verify cryptographically. +- **Confidential GPUs**: Native support for NVIDIA Confidential Computing (H100, Blackwell). -The `` can be either the app ID or instance ID. When using the app ID, the load balancer will select one of the available instances. Adding an `s` suffix enables TLS passthrough to the app instead of terminating at dstack-gateway. Adding a `g` suffix enables HTTPS/2 with TLS termination for gRPC applications. +**Trustless operations** +- **Isolated keys**: Per-app keys derived in TEE. Survives hardware failure. Never exposed to operators. +- **Code governance**: Updates follow predefined rules (e.g., multi-party approval). Operators can't swap code or access secrets. -**Note**: If dstack-gateway is disabled, you'll need to use port mappings configured during deployment to access your application via the host's IP address and mapped ports. +## Getting Started -For development images (`dstack-x.x.x-dev`), you can SSH into the CVM for inspection: - -```bash -# Find the CVM wg IP address in the dstack-vmm dashboard -ssh root@10.0.3.2 -``` - -### Getting TDX Quote in Docker Container - -To get a TDX quote within app containers: - -**1. Mount the socket in `docker-compose.yaml`** +**1. Deploy a private LLM:** ```yaml -version: '3' +# docker-compose.yaml services: - nginx: - image: nginx:latest - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock + vllm: + image: vllm/vllm-openai:latest + runtime: nvidia + command: --model Qwen/Qwen2.5-7B-Instruct ports: - - "8080:80" - restart: always -``` - -**2. Execute the quote request command** - -```bash -# The argument report_data accepts binary data encoding in hex string. -# The actual report_data passing the to the underlying TDX driver is sha2_256(report_data). -curl --unix-socket /var/run/dstack.sock http://localhost/GetQuote?report_data=0x1234deadbeef | jq . -``` - -### Container Logs - -Container logs can be obtained from the CVM's `dashboard` page or by curl: - -```bash -curl 'http://.:9090/logs/?since=0&until=0&follow=true&text=true×tamps=true&bare=true' -``` - -Replace `` and `` with actual values. Available parameters: - -| Parameter | Description | -|-----------|-------------| -| `since=0` | Starting Unix timestamp for log retrieval | -| `until=0` | Ending Unix timestamp for log retrieval | -| `follow` | Enables continuous log streaming | -| `text` | Returns human-readable text instead of base64 encoding | -| `timestamps` | Adds timestamps to each log line | -| `bare` | Returns the raw log lines without json format | - -**Example response:** -```bash -$ curl 'http://0.0.0.0:9190/logs/zk-provider-server?text×tamps' -{"channel":"stdout","message":"2024-09-29T03:05:45.209507046Z Initializing Rust backend...\n"} -{"channel":"stdout","message":"2024-09-29T03:05:45.209543047Z Calling Rust function: init\n"} -{"channel":"stdout","message":"2024-09-29T03:05:45.209544957Z [2024-09-29T03:05:44Z INFO rust_prover] Initializing...\n"} -{"channel":"stdout","message":"2024-09-29T03:05:45.209546381Z [2024-09-29T03:05:44Z INFO rust_prover::groth16] Starting setup process\n"} -``` - -### TLS Passthrough with Custom Domain - -dstack-gateway supports TLS passthrough for custom domains. - -See the example [here](https://github.com/Dstack-TEE/dstack-examples/tree/main/custom-domain/dstack-ingress) for more details. - -### Upgrade an App - -Got to the dstack-vmm webpage, click the **[Upgrade]** button, select or paste the compose file you want to upgrade to, and click the **[Upgrade]** button again. The app id does not change after the upgrade. Stop and start the app to apply the upgrade. - ---- - -## 🔐 Advanced Features - -### Zero Trust HTTPS - -In the tutorial above, we used a TLS certificate with a private key external to the TEE. To establish trust, we need to generate and maintain the certificate's private key within the TEE and provide evidence that all TLS certificates for the domain originated solely from dstack-gateway CVM. - -By combining Certificate Transparency Logs and CAA DNS records, we can make the best effort to minimize security risks. Here's our approach: - -- Set CAA records to allow only the account created in dstack-gateway CVM to request Certificates -- Launch a program to monitor Certificate Transparency Log and give alarm once any certificate issued to a pubkey that isn't generated by dstack-gateway CVM - -#### Configurations - -To launch Certbot, you need to own a domain hosted on Cloudflare. Obtain an API token with DNS operation permissions from the Cloudflare dashboard. Configure it in the `build-config.sh`: - -```bash -# The directory to store the auto obtained TLS certificate and key -GATEWAY_CERT=${CERBOT_WORKDIR}/live/cert.pem -GATEWAY_KEY=${CERBOT_WORKDIR}/live/key.pem - -# For certbot -CF_API_TOKEN=g-DwMH... -# ACME_URL=https://acme-v02.api.letsencrypt.org/directory -ACME_URL=https://acme-staging-v02.api.letsencrypt.org/directory -``` - -Then re-run the build script: - -```bash -../build.sh -``` - -#### Launch Certbot - -Then run the certbot in the `build/` and you will see the following log: - -```text -$ RUST_LOG=info,certbot=debug ./certbot renew -c certbot.toml -2024-10-25T07:41:00.682990Z INFO certbot::bot: creating new ACME account -2024-10-25T07:41:00.869246Z INFO certbot::bot: created new ACME account: https://acme-staging-v02.api.letsencrypt.org/acme/acct/168601853 -... -2024-10-25T07:41:35.852790Z DEBUG certbot::acme_client: setting challenge ready for https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/14584884443/mQ-I2A -2024-10-25T07:41:35.934425Z DEBUG certbot::acme_client: challenges are ready, waiting for order to be ready -2024-10-25T07:41:37.972434Z DEBUG certbot::acme_client: order is ready, uploading csr -2024-10-25T07:41:38.052901Z DEBUG certbot::acme_client: order is processing, waiting for challenge to be accepted -2024-10-25T07:41:40.088190Z DEBUG certbot::acme_client: order is valid, getting certificate -2024-10-25T07:41:40.125988Z DEBUG certbot::acme_client: removing dns record 6ab5724e8fa7e3e8f14e93333a98866a -2024-10-25T07:41:40.377379Z DEBUG certbot::acme_client: stored new cert in /home/kvin/codes/meta-dstack/dstack/build/run/certbot/backup/2024-10-25T07:41:40.377174477Z -2024-10-25T07:41:40.377472Z INFO certbot::bot: checking if certificate needs to be renewed -2024-10-25T07:41:40.377719Z DEBUG certbot::acme_client: will expire in Duration { seconds: 7772486, nanoseconds: 622281542 } -2024-10-25T07:41:40.377752Z INFO certbot::bot: certificate /home/kvin/codes/meta-dstack/dstack/build/run/certbot/live/cert.pem is up to date -``` - -**What the command does:** - -- Registered to letsencrypt and got a new account `https://acme-staging-v02.api.letsencrypt.org/acme/acct/168601853` -- Auto set CAA records for the domain on cloudflare, you can open the CF dashboard to see the record: - -
- -Certbot CAA - -
- -- Auto requested a new certificate from Let's Encrypt. Automatically renews the certificate to maintain its validity - -#### Launch dstack-gateway - -Execute dstack-gateway with `sudo ./dstack-gateway -c gateway.toml`, then access the web portal to check the dstack-gateway CVM managed Let's Encrypt account. The account's private key remains securely sealed within the TEE. - -
- -Gateway Account ID - -
- -### Certificate Transparency Log Monitor - -To enhance security, we've limited TLS certificate issuance to dstack-gateway via CAA records. However, since these records can be modified through Cloudflare's domain management, we need to implement global CA certificate monitoring to maintain security oversight. - -`ct_monitor` tracks Certificate Transparency logs via https://crt.sh, comparing their public key with the ones got from dstack-gateway RPC. It immediately alerts when detecting unauthorized certificates not issued through dstack-gateway: - -```text -$ ./ct_monitor -t https://localhost:9010/prpc -d -2024-10-25T08:12:11.366463Z INFO ct_monitor: monitoring ... -2024-10-25T08:12:11.366488Z INFO ct_monitor: fetching known public keys from https://localhost:9010/prpc -2024-10-25T08:12:11.566222Z INFO ct_monitor: got 2 known public keys -2024-10-25T08:12:13.142122Z INFO ct_monitor: ✅ checked log id=14705660685 -2024-10-25T08:12:13.802573Z INFO ct_monitor: ✅ checked log id=14705656674 -2024-10-25T08:12:14.494944Z ERROR ct_monitor: ❌ error in CTLog { id: 14666084839, issuer_ca_id: 295815, issuer_name: "C=US, O=Let's Encrypt, CN=R11", common_name: "", name_value: "*.", not_before: "2024-09-24T02:23:15", not_after: "2024-12-23T02:23:14", serial_number: "03ae796f56a933c8ff7e32c7c0d662a253d4", result_count: 1, entry_timestamp: "2024-09-24T03:21:45.825" } -2024-10-25T08:12:14.494998Z ERROR ct_monitor: error: certificate has issued to unknown pubkey: 30820122300d06092a864886f70d01010105000382010f003082010a02820101009de65c767caf117880626d1acc1ee78f3c6a992e3fe458f34066f92812ac550190a67e49ebf4f537003c393c000a8ec3e114da088c0cb02ffd0881fd39a2b32cc60d2e9989f0efab3345bee418262e0179d307d8d361fd0837f85d17eab92ec6f4126247e614aa01f4efcc05bc6303a8be68230f04326c9e85406fc4d234e9ce92089253b11d002cdf325582df45d5da42981cd546cbd2e9e49f0fa6636e747a345aaf8cefa02556aa258e1f7f90906be8fe51567ac9626f35bc46837e4f3203387fee59c71cea400000007c24e7537debc1941b36ff1612990233e4c219632e35858b1771f17a71944adf6c657dd7303583e3aeed199bd36a3152f49980f4f30203010001 + - "8000:8000" ``` ---- - -## 🔒 Security +Deploy to any TDX host with the `dstack-nvidia-0.5.x` base image, or use [Phala Cloud](https://cloud.phala.network) for managed infrastructure. -dstack undergoes regular security audits to ensure the highest standards of security for TEE-based deployments. +**2. Verify what's running:** -### Security Audit Report +Every dstack deployment can generate a TDX attestation quote: cryptographic proof of the exact code running inside the confidential VM. -A comprehensive security audit was conducted to evaluate the security architecture and implementation of dstack. The audit report is available at: +Try it: [dstack-demo.phala.com](https://dstack-demo.phala.com) → Click "Verify" → See the proof on [proof.t16z.com](https://proof.t16z.com) -📄 **[Security Audit Report](./docs/security/dstack-audit.pdf)** +Want to deploy a self hosted dstack? Check our [full deployment guide →](./docs/deployment.md) -The audit covers key security aspects including: -- TEE integration and attestation mechanisms -- Cryptographic implementations and key management -- Network security and TLS configurations -- Container isolation and runtime security -- Smart contract security (for blockchain integrations) +## Architecture -For additional security guidance and best practices, see the [Security Guide](./docs/security-guide/security-guide.md). - ---- +![Architecture](./docs/assets/arch.png) -## 🔧 Troubleshooting +Your container runs inside a Confidential VM (Intel TDX) with optional GPU isolation via NVIDIA Confidential Computing. The CPU TEE protects application logic; the GPU TEE protects model weights and inference data. -### Error: qemu-system-x86_64: vhost-vsock: unable to set guest cid: Address already in use +**Core components:** -`dstack-vmm` may throw this error when creating a new VM if the [Unix Socket CID](https://man7.org/linux/man-pages/man7/vsock.7.html) is occupied. +- **Guest Agent**: Runs inside each CVM. Generates TDX attestation quotes so users can verify exactly what's running. Provisions per-app cryptographic keys from KMS. Encrypts local storage. Apps interact via `/var/run/dstack.sock`. -**Solution:** +- **KMS**: Runs in its own TEE. Verifies TDX quotes before releasing keys. Enforces authorization policies defined in on-chain smart contracts — operators cannot bypass these checks. Derives deterministic keys bound to each app's attested identity. -1. **List the occupied CID:** - ```bash - ps aux | grep 'guest-cid=' - ``` +- **Gateway**: Terminates TLS at the edge and provisions ACME certificates automatically. Routes traffic to CVMs. All internal communication uses RA-TLS for mutual attestation. -2. **Choose a new range** of the CID not conflicting with the CID in use. You can change `build/vmm.toml` file and restart `dstack-vmm`. For example, you may find 33000-34000 free to use: - ```toml - [cvm] - cid_start = 33000 - cid_pool_size = 1000 - ``` +- **VMM**: Runs on bare-metal TDX hosts. Parses docker-compose files directly — no app changes needed. Boots CVMs from a reproducible OS image. Allocates CPU, memory, and confidential GPU resources. -3. **When building from scratch**, change the CID configs in `build-config.sh` instead, because `vmm.toml` file is generated by `build.sh`. +[Full security model →](./docs/security.md) -> [!NOTE] -> You may encounter this problem when upgrading from an older version of dstack, because CID was introduced in `build-config.sh` in later versions. In such case, please follow the docs to add the missing entries in `build-config.sh` and rebuild dstack. +## SDKs -### Error: Operation not permitted when building guest image +| Language | Install | Docs | +|----------|---------|------| +| Python | `pip install dstack-sdk` | [README](./sdk/python/README.md) | +| TypeScript | `npm install @phala/dstack-sdk` | [README](./sdk/js/README.md) | +| Rust | `cargo add dstack-sdk` | [README](./sdk/rust/README.md) | +| Go | `go get github.com/Dstack-TEE/dstack/sdk/go` | [README](./sdk/go/README.md) | -When running `../build.sh guest`, you might encounter this error: +## Documentation -``` -Traceback (most recent call last): - File "/meta-dstack/poky/bitbake/bin/bitbake-worker", line 278, in child - bb.utils.disable_network(uid, gid) - File "/meta-dstack/poky/bitbake/lib/bb/utils.py", line 1696, in disable_network - with open("/proc/self/uid_map", "w") as f: -PermissionError: [Errno 1] Operation not permitted -``` +**Use Cases** +- [Private Inference](./docs/inference.md) - vLLM with attestation +- [Secure Agents](./docs/agents.md) - LangChain/Claude SDK with protected credentials +- [Confidential Training](./docs/training.md) - Fine-tuning on sensitive data -**Solution:** +**Guides** +- [Usage Guide](./docs/usage.md) - Deploying and managing apps +- [Deployment](./docs/deployment.md) - Self-hosting on TDX hardware +- [On-Chain Governance](./docs/onchain-governance.md) - Smart contract-based authorization +- [VMM CLI Guide](./docs/vmm-cli-user-guide.md) - Command-line deployment -This error occurs because Ubuntu 23.10 and later versions restrict unprivileged user namespaces by default. +**Reference** +- [Security](./docs/security.md) - Threat model and best practices +- [CVM Boundaries](./docs/cvm-boundaries.md) - Information exchange and isolation +- [App Compose Format](./docs/normalized-app-compose.md) - Compose file specification +- [Gateway](./docs/dstack-gateway.md) - Gateway configuration +- [Design Decisions](./docs/design-and-hardening-decisions.md) - Architecture rationale +- [FAQ](./docs/faq.md) - Frequently asked questions -```bash -sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 -``` +## Community -Then try building again. For more information about this restriction, see the [Ubuntu discourse post](https://discourse.ubuntu.com/t/spec-unprivileged-user-namespace-restrictions-via-apparmor-in-ubuntu-23-10/37626). +[Telegram](https://t.me/+UO4bS4jflr45YmUx) · [GitHub Discussions](https://github.com/Dstack-TEE/dstack/discussions) · [Examples](https://github.com/Dstack-TEE/dstack-examples) ---- +[![Repobeats](https://repobeats.axiom.co/api/embed/0a001cc3c1f387fae08172a9e116b0ec367b8971.svg)](https://github.com/Dstack-TEE/dstack/pulse) -## 🎨 Media Kit +## Media Kit The dstack logo and branding assets are available in the [media kit](./docs/assets/Dstack%20Logo%20Kit/): - **Horizontal logos**: Primary and dark versions in PNG/SVG formats -- **Vertical logos**: Primary and dark versions in PNG/SVG formats +- **Vertical logos**: Primary and dark versions in PNG/SVG formats - **Icons**: Primary and dark versions in PNG/SVG formats -All assets are provided in high-resolution formats suitable for both digital and print use. - ---- - -## 📄 License - -``` -Copyright 2024 Phala Network and Contributors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +## License - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -``` - ---- - -
- -**[⬆ Back to top](#-Overview)** - -Made with ❤️ by the dstack community - -
+Apache 2.0 diff --git a/ct_monitor/Cargo.toml b/ct_monitor/Cargo.toml index 3828526e..5e7dcc96 100644 --- a/ct_monitor/Cargo.toml +++ b/ct_monitor/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true [dependencies] anyhow.workspace = true clap = { workspace = true, features = ["derive"] } +hex = { workspace = true, features = ["alloc", "std"] } hex_fmt.workspace = true regex.workspace = true reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls", "charset", "hickory-dns"] } @@ -21,6 +22,3 @@ tokio = { workspace = true, features = ["full"] } tracing.workspace = true tracing-subscriber.workspace = true x509-parser.workspace = true - -dstack-gateway-rpc.workspace = true -ra-rpc = { workspace = true, default-features = false, features = ["client"] } diff --git a/ct_monitor/src/main.rs b/ct_monitor/src/main.rs index 6a168cce..8f0f268d 100644 --- a/ct_monitor/src/main.rs +++ b/ct_monitor/src/main.rs @@ -4,8 +4,6 @@ use anyhow::{bail, Context, Result}; use clap::Parser; -use dstack_gateway_rpc::gateway_client::GatewayClient; -use ra_rpc::client::RaClient; use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; @@ -15,6 +13,13 @@ use x509_parser::prelude::*; const BASE_URL: &str = "https://crt.sh"; +#[derive(Debug, Deserialize)] +struct AcmeInfoResponse { + #[allow(dead_code)] + account_uri: String, + hist_keys: Vec, +} + struct Monitor { gateway_uri: String, domain: String, @@ -48,12 +53,38 @@ impl Monitor { } async fn refresh_known_keys(&mut self) -> Result<()> { - info!("fetching known public keys from {}", self.gateway_uri); - // TODO: Use RA-TLS - let tls_no_check = true; - let rpc = GatewayClient::new(RaClient::new(self.gateway_uri.clone(), tls_no_check)?); - let info = rpc.acme_info().await?; - self.known_keys = info.hist_keys.into_iter().collect(); + let acme_info_url = format!("{}/acme-info", self.gateway_uri.trim_end_matches('/')); + info!("fetching known public keys from {}", acme_info_url); + + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) // TODO: Use RA-TLS verification + .build() + .context("failed to build http client")?; + + let response = client + .get(&acme_info_url) + .send() + .await + .context("failed to fetch acme-info")?; + + if !response.status().is_success() { + bail!( + "failed to fetch acme-info: HTTP {}", + response.status().as_u16() + ); + } + + let info: AcmeInfoResponse = response + .json() + .await + .context("failed to parse acme-info response")?; + + self.known_keys = info + .hist_keys + .into_iter() + .map(|hex_key| hex::decode(&hex_key).context("invalid hex in hist_keys")) + .collect::>>()?; + info!("got {} known public keys", self.known_keys.len()); for key in self.known_keys.iter() { debug!(" {}", hex_fmt::HexFmt(key)); diff --git a/docs/agents.md b/docs/agents.md new file mode 100644 index 00000000..484f7e0a --- /dev/null +++ b/docs/agents.md @@ -0,0 +1,274 @@ +# Secure Agents & RAG + +Run AI agents with secure key management, protected API credentials, and verifiable execution. + +## Why TEE for Agents? + +Autonomous agents need: +- **Private keys** for blockchain transactions +- **API credentials** for external services (OpenAI, databases, etc.) +- **Sensitive data** in RAG pipelines + +Running in a TEE ensures these secrets remain protected, and attestation proves the agent code hasn't been tampered with. + +## Architecture + +``` +┌────────────────────────────────────────────────────────────┐ +│ dstack CVM (TDX) │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Your Agent │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ +│ │ │ LangChain│ │ Claude │ │ Custom Agent │ │ │ +│ │ │ Agent │ │ Agent SDK│ │ Framework │ │ │ +│ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ +│ │ │ │ │ │ │ +│ │ └─────────────┴─────────────────┘ │ │ +│ │ │ │ │ +│ │ dstack SDK │ │ +│ │ ┌─────────────┴─────────────┐ │ │ +│ │ │ │ │ │ +│ │ get_key() Encrypted Env Vars │ │ +│ │ (wallet keys) (API_KEY, DB_URL) │ │ +│ └───────┴───────────────────────────┴─────────────────┘ │ +│ │ │ +│ /var/run/dstack.sock │ +└────────────────────────────────────────────────────────────┘ +``` + +## Quick Start + +### 1. Docker Compose + +```yaml +# docker-compose.yaml +services: + agent: + image: your-agent-image:latest + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + environment: + - OPENAI_API_KEY # Encrypted at deploy time + - ANTHROPIC_API_KEY # Encrypted at deploy time + - DATABASE_URL # Encrypted at deploy time + ports: + - "8080:8080" +``` + +### 2. Derive Wallet Keys + +Your agent can derive deterministic keys for blockchain operations: + +```python +from dstack_sdk import DstackClient +from dstack_sdk.ethereum import to_account + +client = DstackClient() + +# Derive Ethereum wallet - same path = same key every time +eth_key = client.get_key('agent/wallet', 'mainnet') +account = to_account(eth_key) + +print(f"Agent wallet: {account.address}") + +# Sign transactions securely +signed_tx = account.sign_transaction(tx_dict) +``` + +### 3. Access Protected Credentials + +API keys are encrypted at deploy time and only decrypted inside the TEE: + +```python +import os +from langchain.llms import OpenAI + +# These were encrypted during deployment +# Only accessible inside this specific TEE +api_key = os.environ["OPENAI_API_KEY"] + +llm = OpenAI(api_key=api_key) +``` + +## LangChain Agent Example + +```python +from langchain.agents import initialize_agent, Tool +from langchain.llms import ChatOpenAI +from dstack_sdk import DstackClient +from dstack_sdk.ethereum import to_account +import os + +client = DstackClient() + +# Derive a wallet for the agent +wallet = to_account(client.get_key('agent/eth-wallet', 'mainnet')) + +def check_balance(address: str) -> str: + """Check ETH balance of an address.""" + # ... web3 code + return f"Balance: {balance} ETH" + +def send_eth(to_address: str, amount: str) -> str: + """Send ETH from agent wallet.""" + # Agent's private key never leaves the TEE + tx = wallet.sign_transaction({ + 'to': to_address, + 'value': int(float(amount) * 1e18), + # ... + }) + return f"Sent {amount} ETH to {to_address}" + +tools = [ + Tool(name="CheckBalance", func=check_balance, description="Check ETH balance"), + Tool(name="SendETH", func=send_eth, description="Send ETH to address"), +] + +# API key is encrypted, only accessible in TEE +llm = ChatOpenAI(api_key=os.environ["OPENAI_API_KEY"]) + +agent = initialize_agent(tools, llm, agent="zero-shot-react-description") +agent.run("Send 0.1 ETH to 0x742d35Cc...") +``` + +## Claude Agent SDK Example + +```python +from anthropic import Anthropic +from dstack_sdk import DstackClient +import os + +client = DstackClient() +anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) + +# Agent with TEE-derived signing keys +signing_key = client.get_key('agent/signing', 'production') + +def execute_with_proof(task: str): + """Execute task and provide attestation proof.""" + + # Run the agent + response = anthropic.messages.create( + model="claude-sonnet-4-20250514", + messages=[{"role": "user", "content": task}] + ) + + result = response.content[0].text + + # Generate attestation proving execution + quote = client.get_quote(result.encode()[:64]) + + return { + "result": result, + "attestation": quote.quote, + "signing_address": signing_key.decode_key().hex()[:40] + } +``` + +## RAG with Protected Documents + +```python +from langchain.vectorstores import Chroma +from langchain.embeddings import OpenAIEmbeddings +from dstack_sdk import DstackClient +import os + +client = DstackClient() + +# Embeddings use protected API key +embeddings = OpenAIEmbeddings(api_key=os.environ["OPENAI_API_KEY"]) + +# Documents stored in TEE-encrypted storage +vectorstore = Chroma( + persist_directory="/data/vectors", + embedding_function=embeddings +) + +def rag_query(question: str) -> dict: + """Query with attestation proof.""" + + docs = vectorstore.similarity_search(question, k=3) + answer = generate_answer(question, docs) + + # Prove the query was executed in TEE + quote = client.get_quote(f"{question}:{answer}".encode()[:64]) + + return { + "answer": answer, + "sources": [d.metadata for d in docs], + "attestation": quote.quote + } +``` + +## Deploying with Encrypted Secrets + +When deploying, encrypt sensitive environment variables: + +```python +from dstack_sdk import encrypt_env_vars, EnvVar +import requests + +# Define secrets +env_vars = [ + EnvVar(key='OPENAI_API_KEY', value='sk-...'), + EnvVar(key='ANTHROPIC_API_KEY', value='sk-ant-...'), + EnvVar(key='DATABASE_URL', value='postgresql://...'), + EnvVar(key='WALLET_MNEMONIC', value='abandon abandon...'), +] + +# Get encryption key from dstack KMS +response = requests.post( + 'https://your-dstack/prpc/GetAppEnvEncryptPubKey?json', + json={'app_id': 'your-app-id'} +) +public_key = response.json()['public_key'] + +# Encrypt - only the TEE can decrypt +encrypted = encrypt_env_vars(env_vars, public_key) + +# Deploy with encrypted secrets +deploy_app(compose_file, encrypted_env=encrypted) +``` + +## Verify Agent Execution + +Clients can verify the agent is running unmodified in a TEE: + +```python +import requests + +# Get attestation from agent +attestation = requests.get( + "https://agent-endpoint/attestation", + params={"nonce": "random-challenge"} +).json() + +# Verify TDX quote via proof.t16z.com or programmatically +is_valid = verify_tdx_quote(attestation['quote']) + +# Check agent code hash matches expected +assert attestation['compose_hash'] == EXPECTED_AGENT_HASH +``` + +## Security Guarantees + +| Feature | Protection | +|---------|------------| +| API Keys | Encrypted at deploy, decrypted only in TEE | +| Wallet Keys | Derived deterministically, never leave TEE | +| Agent Code | Measured and attested via TDX | +| Data in Transit | TLS termination inside TEE | +| Execution Proof | TDX quotes for any operation | + +## Production Considerations + +1. **Key Rotation**: Use versioned paths (`agent/wallet/v2`) for key rotation +2. **Rate Limiting**: Implement rate limits on wallet operations +3. **Audit Logging**: Use `emit_event()` for security-critical actions +4. **Multi-Sig**: Derive multiple keys for threshold signatures + +## Source + +- [dstack Python SDK](../sdk/python/README.md) +- [Environment Encryption Guide](../sdk/python/README.md#environment-variables-encryption) diff --git a/docs/security-guide/assets/prelaunch-script.png b/docs/assets/prelaunch-script.png similarity index 100% rename from docs/security-guide/assets/prelaunch-script.png rename to docs/assets/prelaunch-script.png diff --git a/docs/security-guide/assets/token-env.png b/docs/assets/token-env.png similarity index 100% rename from docs/security-guide/assets/token-env.png rename to docs/assets/token-env.png diff --git a/docs/security-guide/cvm-boundaries.md b/docs/cvm-boundaries.md similarity index 99% rename from docs/security-guide/cvm-boundaries.md rename to docs/cvm-boundaries.md index b15c844c..fa5ffbdc 100644 --- a/docs/security-guide/cvm-boundaries.md +++ b/docs/cvm-boundaries.md @@ -117,7 +117,7 @@ dstack uses encrypted environment variables to allow app developers to securely - CVM performs basic regex validation on values - Final result is stored as /dstack/.hostshared/.decrypted-env and loaded system-wide via app-compose.service -This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security-guide.md](security-guide.md) for more details. +This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security.md](security.md) for more details. ### .user-config This is an optional application-specific configuration file that applications inside the CVM can access. dstack OS simply stores it at /dstack/user-config without any measurement or additional processing. diff --git a/docs/deployment.md b/docs/deployment.md index e8466b52..63ff1188 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,35 +1,147 @@ -# Deployment of dstack +# Deploying dstack -This document describes the deployment of dstack components on bare metal TDX hosts. -It contains steps to deploy dstack-kms and dstack-gateway into CVMs. +This guide covers deploying dstack on bare metal TDX hosts. + +## Overview + +dstack can be deployed in two ways: + +- **Dev Deployment**: All components run directly on the host. For local development and testing only - no security guarantees. +- **Production Deployment**: KMS and Gateway run as CVMs with hardware-rooted security. Uses auth server for authorization and OS image whitelisting. Required for any deployment where security matters. ## Prerequisites -- Follow the [TDX setup guide](https://github.com/canonical/tdx) to setup the TDX host. -- Install `cargo` and `rustc` +**Hardware:** +- Bare metal TDX server ([setup guide](https://github.com/canonical/tdx)) +- At least 16GB RAM, 100GB free disk space +- Public IPv4 address +- Optional: NVIDIA H100 or Blackwell GPU for [Confidential Computing](https://www.nvidia.com/en-us/data-center/solutions/confidential-computing/) workloads + +**Network:** +- Domain with DNS access (for Gateway TLS) + +> **Note:** See [Hardware Requirements](https://docs.phala.network/dstack/hardware-requirements) for server recommendations. + +--- + +## Dev Deployment + +This approach runs all components directly on the host for local development and testing. + +> **Warning:** Dev deployment uses KMS in dev mode with no security guarantees. Do NOT use for production. + +### Install Dependencies -## Clone the dstack repository ```bash -git clone https://github.com/Dstack-TEE/dstack +# Ubuntu 24.04 +sudo apt install -y build-essential chrpath diffstat lz4 wireguard-tools xorriso + +# Install Rust +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +### Build Configuration + +```bash +git clone https://github.com/Dstack-TEE/meta-dstack.git --recursive +cd meta-dstack/ +mkdir build && cd build +../build.sh hostcfg +``` + +Edit the generated `build-config.sh` for your environment. The minimal required changes are: + +| Variable | Description | +|----------|-------------| +| `KMS_DOMAIN` | DNS domain for KMS RPC (e.g., `kms.example.com`) | +| `GATEWAY_DOMAIN` | DNS domain for Gateway RPC (e.g., `gateway.example.com`) | +| `GATEWAY_PUBLIC_DOMAIN` | Public base domain for app routing (e.g., `apps.example.com`) | + +**TLS Certificates:** + +The Gateway requires TLS certificates. Configure Certbot with Cloudflare: + +```bash +CERTBOT_ENABLED=true +CF_API_TOKEN= +``` + +The certificates will be obtained automatically via ACME DNS-01 challenge. The KMS auto-generates its own certificates during bootstrap. + +Other variables like ports and CID pool settings have sensible defaults. + +```bash +vim ./build-config.sh +../build.sh hostcfg +``` + +### Download Guest Image + +```bash +../build.sh dl 0.5.5 ``` -## Compile and Run dstack-vmm + +### Run Components + +Start in separate terminals: + +1. **KMS**: `./dstack-kms -c kms.toml` +2. **Gateway**: `sudo ./dstack-gateway -c gateway.toml` +3. **VMM**: `./dstack-vmm -c vmm.toml` + +> **Note:** This deployment uses KMS in dev mode without an auth server. For production deployments with proper security, see [Production Deployment](#production-deployment) below. + +--- + +## Production Deployment + +For production, deploy KMS and Gateway as CVMs with hardware-rooted security. Production deployments require: +- KMS running in a CVM (not on the host) +- Auth server for authorization (not dev mode) +- OS image whitelisting + +For decentralized governance via smart contracts, see [On-Chain Governance](./onchain-governance.md). + +### Production Checklist + +**Required:** + +1. Set up TDX host with dstack-vmm +2. Deploy KMS as CVM (with auth server + OS whitelist) +3. Deploy Gateway as CVM + +**Optional Add-ons:** + +4. [Zero Trust HTTPS](#4-zero-trust-https-optional) +5. [Certificate Transparency monitoring](#5-certificate-transparency-monitoring-optional) +6. [Multi-node deployment](#6-multi-node-deployment-optional) + +--- + +### 1. Set Up TDX Host + +Clone and build dstack-vmm: + ```bash +git clone https://github.com/Dstack-TEE/dstack cd dstack cargo build --release -p dstack-vmm -p supervisor mkdir -p vmm-data cp target/release/dstack-vmm vmm-data/ cp target/release/supervisor vmm-data/ cd vmm-data/ +``` -# create vmm.toml. Edit the config as needed. -cat < vmm.toml +Create `vmm.toml`: + +```toml address = "unix:./vmm.sock" reuse = true image_path = "./images" run_path = "./run/vm" [cvm] -kms_urls = ["https://kms.test2.dstack.phala.network:9201"] +kms_urls = [] gateway_urls = [] cid_start = 30000 cid_pool_size = 1000 @@ -44,332 +156,237 @@ range = [ [host_api] port = 9300 -EOF +``` + +Download guest images from [meta-dstack releases](https://github.com/Dstack-TEE/meta-dstack/releases) and extract to `./images/`. + +> For reproducible builds and verification, see [Security Guide](./security.md). -# Download Guest OS images -DSTACK_VERSION=0.5.2 -wget "https://github.com/Dstack-TEE/meta-dstack/releases/download/v${DSTACK_VERSION}/dstack-${DSTACK_VERSION}.tar.gz" -mkdir -p images/ -tar -xvf dstack-${DSTACK_VERSION}.tar.gz -C images/ -rm -f dstack-${DSTACK_VERSION}.tar.gz +Start VMM: -# run dstack-vmm +```bash ./dstack-vmm -c vmm.toml ``` -## Deploy the DstackKms contract +--- -A KMS node requires a DstackKms contract to be deployed on the Ethereum-compatible network. +### 2. Deploy KMS as CVM -```bash -cd dstack/kms/auth-eth -npm install -npx hardhat compile -PRIVATE_KEY= npx hardhat kms:deploy --with-app-impl --network phala -``` -It will deploy both the DstackApp implementation and DstackKms contract to the Phala network and print the contract addresses: +> **TODO:** This section needs a proper rewrite covering auth server configuration. The current instructions are incomplete. -``` -Step 1: Deploying DstackApp implementation... -✅ DstackApp implementation deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Step 2: Deploying DstackKms... -Deploying proxy... -Waiting for deployment... -DstackKms Proxy deployed to: 0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 -Implementation deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 -Deployment completed successfully -Transaction hash: 0xd413d01a0640b6193048b0e98afb7c173abe58c74d9cf01f368166bc53f4fefe -✅ Complete KMS setup deployed successfully! -- DstackApp implementation: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -- DstackKms proxy: 0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 -🚀 Ready for factory app deployments! -``` +Production KMS deployment requires: +- KMS running inside a CVM +- Auth server for authorization (webhook mode, not dev mode) +- OS image whitelisting -## Deploy KMS into CVM -The dstack-vmm is running now. Open another terminal and go to the `kms/dstack-app/` directory and run the following command: +#### Deploy KMS CVM ```bash cd dstack/kms/dstack-app/ ./deploy-to-vmm.sh ``` -It will create a template `.env` file. Edit the `.env` file and set the required variables. -Especially the `KMS_CONTRACT_ADDR` variable set to the address of the DstackKms Proxy contract deployed in the previous step. -The `IMAGE_DOWNLOAD_URL` variable should be set to the URL of the dstack OS image used to verify the os_image_hash. -``` -# .env + +Edit the generated `.env` file: + +```bash VMM_RPC=unix:../../vmm-data/vmm.sock -KMS_CONTRACT_ADDR=0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 KMS_RPC_ADDR=0.0.0.0:9201 GUEST_AGENT_ADDR=127.0.0.1:9205 -ETH_RPC_URL=https://rpc.phala.network GIT_REV=HEAD -OS_IMAGE=dstack-0.5.2 -IMAGE_DOWNLOAD_URL=https://download.dstack.org/os-images/mr_{OS_IMAGE_HASH}.tar.gz +OS_IMAGE=dstack-0.5.5 ``` -Then run the script again. +Run the script again to deploy: -Then it will deploy the KMS CVM to the dstack-vmm. Outputs: - -``` -App compose file created at: .app-compose.json -Compose hash: ec3d427f62bd60afd520fce0be3b368aba4516434f2ff761f74775f871f5b6e3 -Deploying KMS to dstack-vmm... -App ID: ec3d427f62bd60afd520fce0be3b368aba451643 -Created VM with ID: f5299298-bf4f-43c0-839c-88c755391f3c +```bash +./deploy-to-vmm.sh ``` -Go back to the vmm-data directory and check the status of the KMS CVM: +Monitor startup: + ```bash -cd ../../vmm-data/ -tail -f run/vm/f5299298-bf4f-43c0-839c-88c755391f3c/serial.log +tail -f ../../vmm-data/run/vm//serial.log ``` -Wait until the KMS CVM is ready: -``` -br-1df48b1c448a: port 2(veth36ab5cb) entered forwarding state -app-compose.sh[882]: Container dstack-kms-1 Started -app-compose.sh[688]: Pruning unused images -app-compose.sh[8347]: Total reclaimed space: 0B -app-compose.sh[688]: Pruning unused volumes -app-compose.sh[8356]: Total reclaimed space: 0B -[ OK ] Finished App Compose Service. -[ OK ] Reached target Multi-User System. - Starting Record Runlevel Change in UTMP... -[ OK ] Finished Record Runlevel Change in UTMP. -``` +Wait for `[ OK ] Finished App Compose Service.` -Now open your browser and go to the KMS listening address: -``` -http://127.0.0.1:9201/ -``` -Click Bootstrap button then fill in the domain serving the KMS. For example: `kms.test2.dstack.phala.network`. -![alt text](assets/kms-bootstrap.png) -You should use the domain name that you will use to access the KMS. -Then click [Bootstrap] -> [Finish setup]. -It will display the public key and corresponding TDX quote of the KMS as shown below: -![alt text](assets/kms-bootstrap-result.png) -The KMS info should be then set to the kms-auth-contract [here for this example](https://explorer.phala.network/address/0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875?tab=write_proxy&source_address=0xa1b5B4Eb15E9366D2e11943F5fE319b62a6E9Da3#0x7d025352): -![alt text](assets/kms-auth-set-info.png) -The KMS instance is now ready to use. +#### Bootstrap KMS -## Deploy dstack-gateway in CVM -dstack-gateway can be deployed as a dstack app in the same host as the KMS or in a different host. +Open `http://127.0.0.1:9201/` in your browser. -### Add OS image hash to the KMS whitelist -In order to run user workloads that use the KMS, the OS image hash must be added to the KMS whitelist. +1. Click **Bootstrap** +2. Enter the domain for your KMS (e.g., `kms.example.com`) +3. Click **Finish setup** -The `os_image_hash` is generated during the image build process. It is stored in the `digest.txt` file. +![KMS Bootstrap](assets/kms-bootstrap.png) -After you get the `os_image_hash`, you can register it to the KMS whitelist by running the following command: +The KMS will display its public key and TDX quote: -```bash -cd dstack/kms/auth-eth -npx hardhat kms:add-image --network phala 0x -``` +![KMS Bootstrap Result](assets/kms-bootstrap-result.png) -### Register dstack-gateway in KMS -As a normal dstack app, it requires the app to be registered in the DstackKms contract first. +#### Configure Auth Server -```bash -cd dstack/kms/auth-eth -npx hardhat kms:create-app --network phala --allow-any-device -``` +> **TODO:** Document auth server setup for production KMS. -This will deploy an DstackApp contract in the DstackKms contract and print the app ID: +#### Whitelist OS Image -``` -Deploying with account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Account balance: 9999.988063729279315546 -Initial device: 0xda2d377e04b7133ec1287a18d465fa44ae9dbb08d929166c6bdb414f38a2acd3 -Initial compose hash: none -Using factory method for single-transaction deployment... -Waiting for transaction 0x46cf1959abf309fcde86bcab2518dcf28dd9eec70c74214f0562e7bf847c50de to be confirmed... -✅ App deployed and registered successfully! -Proxy Address (App Id): 0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08 -Owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Transaction hash: 0x46cf1959abf309fcde86bcab2518dcf28dd9eec70c74214f0562e7bf847c50de -Deployed with 1 initial device and 0 initial compose hash -``` +The OS image hash must be whitelisted before apps can run. This is required for both offchain and onchain governance. + +> **TODO:** Document OS image whitelisting for offchain auth server. + +For on-chain governance, see [On-Chain Governance](./onchain-governance.md). + +--- + +### 3. Deploy Gateway as CVM + +> **TODO:** This section is incomplete. Gateway registration with auth server needs to be documented. + +Deploy the Gateway CVM: -Register the app ID to the kms as the gateway app ID: ```bash -npx hardhat kms:set-gateway --network phala 0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08 +cd dstack/gateway/dstack-app/ +./deploy-to-vmm.sh ``` -Now go to the `gateway/dstack-app/` directory and run the following command: +Edit `.env`: + ```bash -cd ../../gateway/dstack-app/ -./deploy-to-vmm.sh +VMM_RPC=unix:../../vmm-data/vmm.sock +CF_API_TOKEN=your_cloudflare_api_token +SRV_DOMAIN=example.com +PUBLIC_IP=$(curl -s ifconfig.me) +ACME_STAGING=yes +OS_IMAGE=dstack-0.5.5 ``` -It will create a template .env file. Edit the .env file and set the required variables. +Deploy: +```bash +./deploy-to-vmm.sh +# Press 'y' to confirm ``` -# .env -VMM_RPC=unix:../../vmm-data/vmm.sock -# Cloudflare API token for DNS challenge used to get the SSL certificate. -CF_API_TOKEN=your_cloudflare_api_token +Update `vmm.toml` with KMS and Gateway URLs: -# Service domain -SRV_DOMAIN=test2.dstack.phala.network +```toml +[cvm] +kms_urls = ["https://kms.example.com:9201"] +gateway_urls = ["https://gateway.example.com:9202"] +``` -# Public IP address -PUBLIC_IP=$(curl -s ifconfig.me) +Restart dstack-vmm. -# Gateway application ID. Register the app in DstackKms first to get the app ID. -GATEWAY_APP_ID=0x31884c4b7775affe4c99735f6c2aff7d7bc6cfcd +> **TODO:** Document Gateway registration with offchain auth server. -# Whether to use ACME staging (yes/no) -ACME_STAGING=yes +--- -# Subnet index. 0~15 -SUBNET_INDEX=0 +### 4. Zero Trust HTTPS (Optional) -# My URL. The URL will be synced to other nodes in the cluster so that each node can discover other nodes. -MY_URL=https://gateway.test2.dstack.phala.network:9202 +Generate TLS certificates inside the TEE with automatic CAA record management. -# Bootnode URL. If you want to deploy a multi-node dstack-gateway cluster, set the bootnode URL to the URL of another node already deployed or planed to be deployed later. -BOOTNODE_URL=https://gateway.test2.dstack.phala.network:9202 +Configure in `build-config.sh`: -# dstack OS image name -OS_IMAGE=dstack-0.5.2 +```bash +GATEWAY_CERT=${CERBOT_WORKDIR}/live/cert.pem +GATEWAY_KEY=${CERBOT_WORKDIR}/live/key.pem +CF_API_TOKEN= +ACME_URL=https://acme-v02.api.letsencrypt.org/directory +``` -# Set defaults for variables that might not be in .env -GIT_REV=HEAD +Run certbot: -# Port configurations -GATEWAY_RPC_ADDR=0.0.0.0:9202 -GATEWAY_ADMIN_RPC_ADDR=127.0.0.1:9203 -GATEWAY_SERVING_ADDR=0.0.0.0:9204 -GUEST_AGENT_ADDR=127.0.0.1:9206 -WG_ADDR=0.0.0.0:9202 +```bash +RUST_LOG=info,certbot=debug ./certbot renew -c certbot.toml ``` -Then run the script again. -It should show the prompt to confirm the deployment: -``` -App compose file created at: .app-compose.json -Compose hash: 700a50336df7c07c82457b116e144f526c29f6d8f4a0946b3e88065c9beba0f4 -Configuration: -VMM_RPC: unix:../../build/vmm.sock -SRV_DOMAIN: test5.dstack.phala.network -PUBLIC_IP: 66.220.6.113 -GATEWAY_APP_ID: 31884c4b7775affe4c99735f6c2aff7d7bc6cfcd -MY_URL: https://gateway.test5.dstack.phala.network:9202 -BOOTNODE_URL: https://gateway.test2.dstack.phala.network:9202 -SUBNET_INDEX: 0 -WG_ADDR: 0.0.0.0:9202 -GATEWAY_RPC_ADDR: 0.0.0.0:9202 -GATEWAY_ADMIN_RPC_ADDR: 127.0.0.1:9203 -GATEWAY_SERVING_ADDR: 0.0.0.0:9204 -GUEST_AGENT_ADDR: 127.0.0.1:9206 -Continue? [y/N] -``` +This will: +- Create an ACME account +- Set CAA DNS records on Cloudflare +- Request and auto-renew certificates -Don't press `y` yet. We need to add the compose hash to the DstackApp contract first. Go back to the `kms/auth-eth` directory and run the following command: +--- + +### 5. Certificate Transparency Monitoring (Optional) + +Monitor for unauthorized certificates issued to your domain. ```bash -npx hardhat app:add-hash --network phala --app-id 0x31884c4b7775affe4c99735f6c2aff7d7bc6cfcd 0x700a50336df7c07c82457b116e144f526c29f6d8f4a0946b3e88065c9beba0f4 +cargo build --release -p ct_monitor +./target/release/ct_monitor \ + --gateway-uri https:// \ + --domain ``` -After the transaction is confirmed, you can press `y` to continue the deployment. +**How it works:** +1. Fetches known public keys from Gateway (`/acme-info` endpoint) +2. Queries crt.sh for certificates issued to your domain +3. Verifies each certificate's public key matches the known keys +4. Logs errors (❌) when certificates are issued to unknown public keys -Similar to the KMS deployment, it will deploy the dstack-gateway CVM to the dstack-vmm and it will start serving later. +The monitor runs in a loop, checking every 60 seconds. Integrate with your alerting system by monitoring stderr for error messages. -## Deploy dstack-vmm on other TDX hosts to serve user workloads -After the KMS and dstack-gateway are deployed, you can deploy dstack-vmm on other TDX hosts to serve user workloads. -You can follow the steps at the beginning of this document to deploy dstack-vmm on other TDX hosts. -Edit the vmm.toml file to set the KMS and dstack-gateway URLs. +--- -``` -# vmm.toml -[cvm] -kms_urls = ["https://kms.test2.dstack.phala.network:9201"] -gateway_urls = ["https://gateway.test2.dstack.phala.network:9202"] -``` +### 6. Multi-Node Deployment (Optional) -Then restart the dstack-vmm. +Scale by adding VMM nodes pointing to your existing KMS and Gateway. -## Deploy app on the dstack-vmm +On each additional TDX host: +1. Set up dstack-vmm (see step 1) +2. Configure `vmm.toml` with existing KMS/Gateway URLs +3. Start VMM -After the dstack-vmm is ready, you can deploy an app on it following the steps below. +```toml +[cvm] +kms_urls = ["https://kms.example.com:9201"] +gateway_urls = ["https://gateway.example.com:9202"] +``` -### 1. On-chain Registration +--- -The on-chain registration process includes two steps: +## Deploying Apps -1. Deploy an App's control contract DstackApp. Developers can develop their own or choose the reference contract from the dstack repository. Custom contracts need to implement the IAppAuth interface. -2. Call DstackKms.registerApp(appContractAddress) to register the contract. +After setup, deploy apps via the VMM dashboard or CLI. -The dstack repository provides scripts to complete these two steps: +> **TODO:** Document app registration with offchain auth server. -**Option 1: Traditional deployment (2 transactions)** -```bash -git clone https://github.com/Dstack-TEE/dstack -cd dstack/kms/auth-eth -npm install -npx hardhat compile -export PRIVATE_KEY= -export KMS_CONTRACT_ADDRESS=0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 -npx hardhat app:deploy --allow-any-device --network phala -``` +For on-chain governed apps, see [On-Chain Governance](./onchain-governance.md). -**Option 2: Factory deployment (1 transaction, recommended)** -```bash -npx hardhat kms:create-app --allow-any-device --network phala -``` +### Deploy via UI -Command output: -``` -Deploying with account: 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199 -Account balance: 9999.995278992293365404 -App ID: 0xA35b434eE853fdf9c2Bf48Fa1583Ac1332d50255 -Starting DstackApp deployment process... -Deploying proxy... -Waiting for deployment... -DstackApp deployed to: 0xD4a546B1C7e63CD4CeD314b2C90108e49191A915 -Implementation deployed to: 0x5aC1671E1Df54994D023F0B05806821d6D84e086 -Deployment completed successfully -Transaction hash: 0xceac2ac6d56a40fef903b947d3a05df42ccce66da7f356c5d54afda68277f9a9 -Waiting for transaction 0xe144e9007208079e5e82c04f727d2383c58184e74d4f860e62557b5f330ab832 to be confirmed... -✅ App deployed and registered successfully! -App ID: 0xA35b434eE853fdf9c2Bf48Fa1583Ac1332d50255 -Proxy Address: 0xD4a546B1C7e63CD4CeD314b2C90108e49191A915 -Owner: 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199 -Transaction hash: 0xe144e9007208079e5e82c04f727d2383c58184e74d4f860e62557b5f330ab832 -``` +Open `http://localhost:9080`: -Note the AppId, which needs to be filled in when deploying cvm. +![App Deploy](assets/app-deploy.png) -**Additional options:** -- Add initial device ID: `--device 0x1234...` -- Add initial compose hash: `--hash 0x5678...` -- Both deployment methods support these optional parameters for pre-configuration during deployment. +- Select the OS image +- Enter the App ID (from auth server registration) +- Upload your `docker-compose.yaml` -If you need to upgrade the contract in the future, please backup the `.openzeppelin/unknown-2035.json` file. +After startup, click **Dashboard** to view: -### 2. Add the App compose hash to the whitelist +![App Board](assets/app-board.png) -Build app-compose.json and calculate its sha256 to get compose-hash. The compose hash can also be previewed in the dstack-vmm UI. +--- -Call the hardhat command to add it to the whitelist (using DstackApp as an example here; custom DstackApp follows its own custom permission control logic). +## Troubleshooting -```bash -export PRIVATE_KEY= -npx hardhat app:add-hash --network phala --app-id 0xA35b434eE853fdf9c2Bf48Fa1583Ac1332d50255 0x44d9cb98aaa6ab11f5729fc7d6fd58117585e0e3fbec621612dcee6b2dfbcde5 -``` +### Error: vhost-vsock: unable to set guest cid: Address already in use -### 3. Deploy instances using dstack-vmm +The CID range conflicts with existing VMs. -![app deploy](assets/app-deploy.png) -- Select image `dstack-0.4.2` -- Fill in the AppId applied in the contract during deployment -- Currently, test DstackKms has set a whitelist for Base image, requiring instance memory to be `≥ 3G` or exactly `= 2G` +1. Find used CIDs: `ps aux | grep 'guest-cid='` +2. Update `vmm.toml`: + ```toml + [cvm] + cid_start = 33000 + cid_pool_size = 1000 + ``` -After the app starts normally, click [Board] to access. +### Error: Operation not permitted when building guest image -You can find the connections to dstack-gateway nodes, meaning that the app is now reachable from the internet: +Ubuntu 23.10+ restricts unprivileged user namespaces: -![app board](assets/app-board.png) +```bash +sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 +``` diff --git a/docs/inference.md b/docs/inference.md new file mode 100644 index 00000000..7153997e --- /dev/null +++ b/docs/inference.md @@ -0,0 +1,112 @@ +# Private Inference + +Run LLM inference with hardware attestation and cryptographic proof of responses. + +## Architecture + +``` +┌─────────────┐ ┌──────────────────────────────────────────────┐ +│ Client │ │ dstack CVM (TDX) │ +│ │ │ ┌─────────────┐ ┌─────────────────┐ │ +│ Request ───┼────►│ │ vllm-proxy │──────│ vLLM Backend │ │ +│ │ │ │ (attestation│ │ (OpenAI API) │ │ +│ ◄──────────┼─────│ │ + signing) │◄─────│ │ │ +│ Response │ │ └─────────────┘ └─────────────────┘ │ +│ + Signature│ │ │ │ +│ + TDX Quote│ │ └── /var/run/dstack.sock │ +└─────────────┘ └──────────────────────────────────────────────┘ +``` + +**vllm-proxy** adds a security layer to vLLM: +- **Hardware attestation** — TDX quotes proving execution in secure hardware +- **Response signing** — Every response cryptographically signed (ECDSA + ED25519) +- **GPU attestation** — NVIDIA Confidential Computing verification (when available) + +## Quick Start + +```yaml +services: + vllm-proxy: + image: dstacktee/vllm-proxy:latest + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + environment: + - MODEL_NAME=openai/gpt-oss-20b + ports: + - "8000:8000" + + vllm: + image: vllm/vllm-openai:latest + runtime: nvidia + environment: + - NVIDIA_VISIBLE_DEVICES=all + command: --model openai/gpt-oss-20b +``` + +Deploy to [Phala Cloud](https://cloud.phala.network) or any dstack instance. + +## Usage + +Standard OpenAI-compatible API: + +```python +from openai import OpenAI + +client = OpenAI( + base_url="https://your-endpoint:8000/v1", + api_key="your-token" +) + +response = client.chat.completions.create( + model="openai/gpt-oss-20b", + messages=[{"role": "user", "content": "Hello!"}] +) + +print(response.choices[0].message.content) +``` + +## Verify + +Every response can be verified. Retrieve the signature and attestation: + +```python +import requests + +# Get signature for a chat completion +chat_id = response.id +sig = requests.get(f"https://your-endpoint:8000/v1/signature/{chat_id}").json() + +print(f"Request hash: {sig['request_hash']}") +print(f"Response hash: {sig['response_hash']}") +print(f"ECDSA signature: {sig['ecdsa_signature']}") +print(f"Signing address: {sig['signing_address']}") + +# Get attestation report +attestation = requests.get( + f"https://your-endpoint:8000/v1/attestation", + params={"nonce": "your-random-nonce"} +).json() + +print(f"TDX quote: {attestation['tdx_quote'][:100]}...") +print(f"GPU evidence: {attestation.get('gpu_evidence', 'N/A')}") +``` + +Or paste the attestation into [proof.t16z.com](https://proof.t16z.com) for visual verification. + +The attestation binds the signing key to the TEE hardware. Verify: +1. TDX quote is valid (via [proof.t16z.com](https://proof.t16z.com) or dstack verifier) +2. Signing address in quote matches the one that signed responses +3. Response hash matches your received content + +## Live Demo + +Try it: [dstack-demo.phala.com](https://dstack-demo.phala.com) + +## Production Users + +- **redpill.ai** — Verifiable AI inference platform +- **NEAR AI** — Decentralized AI services + +## Source + +[github.com/Dstack-TEE/vllm-proxy](https://github.com/Dstack-TEE/vllm-proxy) diff --git a/docs/onchain-governance.md b/docs/onchain-governance.md new file mode 100644 index 00000000..81d3a253 --- /dev/null +++ b/docs/onchain-governance.md @@ -0,0 +1,138 @@ +# On-Chain Governance + +This guide covers setting up on-chain governance for dstack using smart contracts on Ethereum. + +## Overview + +On-chain governance adds: +- **Smart contract-based authorization**: App registration and whitelisting managed by smart contracts +- **Decentralized trust**: No single operator controls keys +- **Transparent policies**: Anyone can verify authorization rules on-chain + +## Prerequisites + +- Production dstack deployment with KMS and Gateway as CVMs (see [Deployment Guide](./deployment.md)) +- Ethereum wallet with funds on Sepolia testnet (or your target network) +- Node.js and npm installed + +## Deploy DstackKms Contract + +```bash +cd dstack/kms/auth-eth +npm install +npx hardhat compile +PRIVATE_KEY= npx hardhat kms:deploy --with-app-impl --network sepolia +``` + +Note the deployed contract address. + +## Configure KMS for On-Chain Auth + +Update KMS to use webhook auth mode pointing to your auth-api service. + +> **TODO:** Document auth-api service deployment and KMS webhook configuration. + +## Whitelist OS Image + +```bash +cd dstack/kms/auth-eth +npm install +npx hardhat kms:add-image --network sepolia 0x +``` + +The `os_image_hash` is in the `digest.txt` file from the image build. + +## Register Gateway App + +```bash +npx hardhat kms:create-app --network sepolia --allow-any-device +``` + +Note the App ID from the output. + +Set it as the gateway app: + +```bash +npx hardhat kms:set-gateway --network sepolia +``` + +Add the compose hash to the whitelist: + +```bash +npx hardhat app:add-hash --network sepolia --app-id +``` + +## Register Apps On-Chain + +For each app you want to deploy: + +### Create App + +```bash +npx hardhat kms:create-app --network sepolia --allow-any-device +``` + +### Add Compose Hash + +```bash +npx hardhat app:add-hash --network sepolia --app-id +``` + +### Deploy via VMM + +Use the App ID when deploying through the VMM dashboard or CLI. + +## Smart Contract Reference + +### DstackKms (Main Contract) + +The central governance contract that manages OS image whitelisting, app registration, and KMS authorization. + +| Function | Description | +|----------|-------------| +| `addOsImageHash(bytes32)` | Whitelist an OS image hash | +| `removeOsImageHash(bytes32)` | Remove an OS image from whitelist | +| `setGatewayAppId(string)` | Set the trusted Gateway app ID | +| `registerApp(address)` | Register an app contract | +| `deployAndRegisterApp(...)` | Deploy and register app in one transaction | +| `isAppAllowed(AppBootInfo)` | Check if an app is allowed to boot | +| `isKmsAllowed(AppBootInfo)` | Check if KMS is allowed to boot | + +### DstackApp (Per-App Contract) + +Each app has its own contract controlling which compose hashes and devices are allowed. + +| Function | Description | +|----------|-------------| +| `addComposeHash(bytes32)` | Whitelist a compose hash | +| `removeComposeHash(bytes32)` | Remove a compose hash from whitelist | +| `addDevice(bytes32)` | Whitelist a device ID | +| `removeDevice(bytes32)` | Remove a device from whitelist | +| `setAllowAnyDevice(bool)` | Allow any device to run this app | +| `isAppAllowed(AppBootInfo)` | Check if app can boot with given config | +| `disableUpgrades()` | Permanently disable contract upgrades | + +### AppBootInfo Structure + +Both `isAppAllowed` and `isKmsAllowed` take an `AppBootInfo` struct: + +```solidity +struct AppBootInfo { + address appId; // Unique app identifier (contract address) + bytes32 composeHash; // Hash of docker-compose configuration + address instanceId; // Unique instance identifier + bytes32 deviceId; // Hardware device identifier + bytes32 mrAggregated; // Aggregated measurement register + bytes32 mrSystem; // System measurement register + bytes32 osImageHash; // OS image hash + string tcbStatus; // TCB status (e.g., "UpToDate") + string[] advisoryIds; // Security advisory IDs +} +``` + +Source: [`kms/auth-eth/contracts/`](../kms/auth-eth/contracts/) + +## See Also + +- [Deployment Guide](./deployment.md) - Setting up dstack infrastructure +- [Security Guide](./security.md) - Security best practices diff --git a/docs/security-guide/security-guide.md b/docs/security.md similarity index 95% rename from docs/security-guide/security-guide.md rename to docs/security.md index f0e42f83..6bfed541 100644 --- a/docs/security-guide/security-guide.md +++ b/docs/security.md @@ -38,11 +38,11 @@ dstack provides encrypted environment variable functionality. Although the CVM p If you use dstack-vmm's built-in UI, the prelaunch script has already been automatically filled in for you: -![alt text](assets/prelaunch-script.png) +![Prelaunch Script](./assets/prelaunch-script.png) You only need to add the `APP_LAUNCH_TOKEN` environment variable to enable LAUNCH_TOKEN checking. -![alt text](assets/token-env.png) +![Token Environment Variable](./assets/token-env.png) user_config is not encrypted, and similarly requires integrity checks at the application layer. For example, you can store a USER_CONFIG_HASH in encrypted environment variables and verify it in the prelaunch script. @@ -119,3 +119,7 @@ services: image: nginx@sha256:eee5eae48e79b2e75178328c7c585b89d676eaae616f03f9a1813aaed820745a network_mode: host ``` + +## Security Audit + +dstack has undergone a security audit. See the [full audit report](./security/dstack-audit.pdf). diff --git a/docs/training.md b/docs/training.md new file mode 100644 index 00000000..340b2ea1 --- /dev/null +++ b/docs/training.md @@ -0,0 +1,310 @@ +# Confidential Training + +Fine-tune LLMs on sensitive data with hardware-enforced privacy. + +## Why TEE for Training? + +Training data is often the most sensitive asset: +- **Medical records** for healthcare AI +- **Financial data** for fraud detection +- **Proprietary documents** for enterprise RAG +- **User conversations** for personalization + +Running training in a TEE ensures: +- Data is encrypted in memory +- Model weights stay protected +- Training code is attested and verifiable + +## Architecture + +``` +┌──────────────────────────────────────────────────────────────┐ +│ dstack CVM (TDX) │ +│ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ Training Pipeline │ │ +│ │ │ │ +│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ +│ │ │ Encrypted│ │ Unsloth │ │ Fine-tuned │ │ │ +│ │ │ Dataset │───►│ / LoRA │───►│ Model │ │ │ +│ │ │ │ │ Training │ │ (encrypted) │ │ │ +│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ +│ │ ▲ │ │ │ +│ │ │ ▼ │ │ +│ │ Decrypted Model export │ │ +│ │ only in TEE with attestation │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +│ GPU: NVIDIA H100 (Confidential Computing) │ +└──────────────────────────────────────────────────────────────┘ +``` + +## Quick Start + +### Docker Compose + +```yaml +# docker-compose.yaml +services: + trainer: + image: unsloth/unsloth:latest + runtime: nvidia + environment: + - NVIDIA_VISIBLE_DEVICES=all + - HF_TOKEN # Encrypted at deploy time + - WANDB_API_KEY # Encrypted at deploy time + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + - /mnt/data:/data # Encrypted dataset + - /mnt/models:/models # Model output + command: python train.py +``` + +### Training Script + +```python +# train.py +from unsloth import FastLanguageModel +from dstack_sdk import DstackClient +import os + +client = DstackClient() + +# Verify we're running in TEE +info = client.info() +print(f"Training in TEE: {info.app_id}") +print(f"TCB Info: {info.tcb_info}") + +# Load base model +model, tokenizer = FastLanguageModel.from_pretrained( + model_name="unsloth/Llama-3.2-3B-Instruct", + max_seq_length=2048, + load_in_4bit=True, +) + +# Add LoRA adapters +model = FastLanguageModel.get_peft_model( + model, + r=16, + lora_alpha=16, + lora_dropout=0, + target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], +) + +# Load sensitive training data (decrypted only in TEE) +from datasets import load_dataset +dataset = load_dataset("json", data_files="/data/training.json") + +# Train +from trl import SFTTrainer +from transformers import TrainingArguments + +trainer = SFTTrainer( + model=model, + train_dataset=dataset["train"], + args=TrainingArguments( + output_dir="/models/output", + per_device_train_batch_size=2, + gradient_accumulation_steps=4, + num_train_epochs=3, + learning_rate=2e-4, + fp16=True, + logging_steps=10, + save_strategy="epoch", + ), + tokenizer=tokenizer, +) + +trainer.train() + +# Save with attestation proof +model.save_pretrained("/models/finetuned") + +# Generate attestation for the training run +quote = client.get_quote(b"training-complete") +with open("/models/finetuned/attestation.txt", "w") as f: + f.write(quote.quote) + +print("Training complete with attestation proof") +``` + +## Encrypted Dataset Upload + +Encrypt datasets before uploading: + +```python +from dstack_sdk import encrypt_env_vars, EnvVar +import requests +import json + +# Encrypt the dataset content +dataset = json.dumps([ + {"instruction": "...", "response": "..."}, + # ... sensitive training examples +]) + +env_vars = [EnvVar(key='TRAINING_DATA', value=dataset)] + +# Get encryption key from KMS +response = requests.post( + 'https://your-dstack/prpc/GetAppEnvEncryptPubKey?json', + json={'app_id': 'training-app-id'} +) +public_key = response.json()['public_key'] + +encrypted = encrypt_env_vars(env_vars, public_key) +# Deploy with encrypted dataset +``` + +For large datasets, use encrypted volumes or secure data transfer protocols. + +## Verify Training Provenance + +After training, verify the model was trained in a TEE: + +```python +import requests + +# Get training attestation +with open("model/attestation.txt") as f: + quote = f.read() + +# Verify the quote +# Option 1: Paste into proof.t16z.com for visual verification +# Option 2: Programmatic verification +verification = verify_tdx_quote(quote) + +print(f"Valid TEE: {verification['valid']}") +print(f"App hash: {verification['compose_hash']}") +``` + +## Multi-GPU Training + +For distributed training across multiple GPUs: + +```yaml +# docker-compose.yaml +services: + trainer: + image: unsloth/unsloth:latest + runtime: nvidia + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NCCL_DEBUG=INFO + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + - /mnt/data:/data + - /mnt/models:/models + command: > + torchrun --nproc_per_node=4 train.py + --model unsloth/Llama-3.2-8B-Instruct + --data /data/training.json +``` + +## Continuous Training Pipeline + +```python +from dstack_sdk import DstackClient +import schedule +import time + +client = DstackClient() + +def training_job(): + """Run scheduled training with attestation.""" + + # Log training start + client.emit_event("training", json.dumps({ + "status": "started", + "timestamp": time.time() + })) + + try: + # Run training + run_training() + + # Generate success attestation + quote = client.get_quote(b"training-success") + upload_model_with_attestation(quote.quote) + + client.emit_event("training", json.dumps({ + "status": "completed", + "timestamp": time.time() + })) + + except Exception as e: + client.emit_event("training", json.dumps({ + "status": "failed", + "error": str(e), + "timestamp": time.time() + })) + raise + +# Schedule daily training +schedule.every().day.at("02:00").do(training_job) + +while True: + schedule.run_pending() + time.sleep(60) +``` + +## NVIDIA Confidential Computing + +For full GPU memory encryption, use NVIDIA H100 with Confidential Computing: + +```yaml +# docker-compose.yaml with CC-enabled GPU +services: + trainer: + image: unsloth/unsloth:latest + runtime: nvidia + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_CC_ACCEPT_EULA=accept # Enable Confidential Computing + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock +``` + +With CC-enabled GPUs: +- GPU memory is encrypted +- Model weights protected during training +- Gradient data stays confidential + +## Security Guarantees + +| Stage | Protection | +|-------|------------| +| Dataset at rest | Encrypted storage | +| Dataset in memory | TDX memory encryption | +| Model weights | TDX + GPU CC memory encryption | +| Gradients | Never leave TEE | +| Output model | Attestation-signed | + +## Frameworks Tested + +| Framework | Status | Notes | +|-----------|--------|-------| +| Unsloth | ✅ Production | Fast LoRA fine-tuning | +| Hugging Face TRL | ✅ Production | Full RLHF support | +| PyTorch | ✅ Production | Native training | +| DeepSpeed | ✅ Tested | Distributed training | +| Axolotl | ✅ Tested | Config-driven training | + +## Production Considerations + +1. **Checkpointing**: Save checkpoints to encrypted storage +2. **Logging**: Use `emit_event()` for audit trails +3. **Data Validation**: Verify dataset integrity before training +4. **Model Export**: Sign models with TEE-derived keys + +## Source + +- [Unsloth](https://github.com/unslothai/unsloth) - Fast LLM fine-tuning +- [dstack Python SDK](../sdk/python/README.md) diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..64684c6c --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,119 @@ +# dstack Usage Guide + +This guide covers deploying and managing applications on dstack. For infrastructure setup and self-hosting, see the [Deployment Guide](./deployment.md). + +You can manage VMs via the dashboard or [CLI](./vmm-cli-user-guide.md). + +## Deploy an App + +Open the dstack-vmm webpage [http://localhost:9080](http://localhost:9080) (change the port according to your configuration) on your local machine to deploy a `docker-compose.yaml` file: + +
+VMM Interface +
+ +After the container is deployed, it should take some time to start the CVM and the containers. Time would vary depending on your workload. + +- **[Logs]**: Click this button to view the CVM logs and monitor container startup progress +- **[Dashboard]**: Once the container is running, click this button to view container information and logs. (Note: This button is only visible when dstack-gateway is enabled. If disabled, you'll need to add a port mapping to port 8090 to access the CVM dashboard) + +
+Guest Agent Dashboard +
+ +## Pass Secrets to Apps + +When deploying a new App, you can pass private data via Encrypted Environment Variables. These variables can be referenced in the docker-compose.yaml file as shown below: + +
+Secret Management +
+ +The environment variables will be encrypted on the client-side and decrypted in the CVM before being passed to the containers. + +## Access the App + +Once your app is deployed and listening on an HTTP port, you can access it through dstack-gateway's public domain using these ingress mapping rules: + +- `[-[][s|g]].` maps to port `` in the CVM + +**Examples:** + +- `3327603e03f5bd1f830812ca4a789277fc31f577-8080.test0.dstack.org` - port `8080` (TLS termination to any TCP) +- `3327603e03f5bd1f830812ca4a789277fc31f577-8080g.test0.dstack.org` - port `8080` (TLS termination with HTTP/2 negotiation) +- `3327603e03f5bd1f830812ca4a789277fc31f577-8080s.test0.dstack.org` - port `8080` (TLS passthrough to any TCP) + +The `` can be either the app ID or instance ID. When using the app ID, the load balancer will select one of the available instances. Adding an `s` suffix enables TLS passthrough to the app instead of terminating at dstack-gateway. Adding a `g` suffix enables HTTPS/2 with TLS termination for gRPC applications. + +**Note:** If dstack-gateway is disabled, you'll need to use port mappings configured during deployment to access your application via the host's IP address and mapped ports. + +For development images (`dstack-x.x.x-dev`), you can SSH into the CVM for inspection: + +```bash +# Find the CVM wg IP address in the dstack-vmm dashboard +ssh root@10.0.3.2 +``` + +## Getting TDX Quote in Docker Container + +To get a TDX quote within app containers: + +**1. Mount the socket in `docker-compose.yaml`** + +```yaml +version: '3' +services: + nginx: + image: nginx:latest + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + ports: + - "8080:80" + restart: always +``` + +**2. Execute the quote request command** + +```bash +# The argument report_data accepts binary data encoding in hex string. +# The actual report_data passing to the underlying TDX driver is sha2_256(report_data). +curl --unix-socket /var/run/dstack.sock http://localhost/GetQuote?report_data=0x1234deadbeef | jq . +``` + +## Container Logs + +Container logs can be obtained from the CVM's `dashboard` page or by curl: + +```bash +curl 'http://.:9090/logs/?since=0&until=0&follow=true&text=true×tamps=true&bare=true' +``` + +Replace `` and `` with actual values. Available parameters: + +| Parameter | Description | +|-----------|-------------| +| `since=0` | Starting Unix timestamp for log retrieval | +| `until=0` | Ending Unix timestamp for log retrieval | +| `follow` | Enables continuous log streaming | +| `text` | Returns human-readable text instead of base64 encoding | +| `timestamps` | Adds timestamps to each log line | +| `bare` | Returns the raw log lines without json format | + +**Example response:** +```bash +$ curl 'http://0.0.0.0:9190/logs/zk-provider-server?text×tamps' +{"channel":"stdout","message":"2024-09-29T03:05:45.209507046Z Initializing Rust backend...\n"} +{"channel":"stdout","message":"2024-09-29T03:05:45.209543047Z Calling Rust function: init\n"} +{"channel":"stdout","message":"2024-09-29T03:05:45.209544957Z [2024-09-29T03:05:44Z INFO rust_prover] Initializing...\n"} +{"channel":"stdout","message":"2024-09-29T03:05:45.209546381Z [2024-09-29T03:05:44Z INFO rust_prover::groth16] Starting setup process\n"} +``` + +## TLS Passthrough with Custom Domain + +dstack-gateway supports TLS passthrough for custom domains. + +See the example [here](https://github.com/Dstack-TEE/dstack-examples/tree/main/custom-domain/dstack-ingress) for more details. + +## Upgrade an App + +Go to the dstack-vmm webpage, click the **[Upgrade]** button, select or paste the compose file you want to upgrade to, and click the **[Upgrade]** button again. The app id does not change after the upgrade. Stop and start the app to apply the upgrade. diff --git a/kms/auth-eth/lib/forge-std b/kms/auth-eth/lib/forge-std new file mode 160000 index 00000000..77041d2c --- /dev/null +++ b/kms/auth-eth/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505 diff --git a/kms/auth-eth/lib/openzeppelin-contracts-upgradeable b/kms/auth-eth/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..e725abdd --- /dev/null +++ b/kms/auth-eth/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit e725abddf1e01cf05ace496e950fc8e243cc7cab diff --git a/kms/auth-eth/lib/openzeppelin-foundry-upgrades b/kms/auth-eth/lib/openzeppelin-foundry-upgrades new file mode 160000 index 00000000..cbce1e00 --- /dev/null +++ b/kms/auth-eth/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit cbce1e00305e943aa1661d43f41e5ac72c662b07 From 6d23c437ee808fc00ee0b1b28790c7ecac84bd86 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Sat, 27 Dec 2025 04:23:43 +0000 Subject: [PATCH 02/16] docs: improve on-chain governance guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ALCHEMY_API_KEY to prerequisites and deploy command - Add KMS_CONTRACT_ADDRESS export after deployment (critical fix) - Clarify KMS_CONTRACT_ADDR vs KMS_CONTRACT_ADDRESS usage - Add sample outputs for all commands - Add compose hash computation instructions - Add cross-references to related docs - Writing guide: add procedural documentation section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .agent/WRITING_GUIDE.md | 27 ++++++++ docs/deployment.md | 129 ++++++++++++++++++++++++++++++------- docs/onchain-governance.md | 71 +++++++++++++++++--- 3 files changed, 192 insertions(+), 35 deletions(-) diff --git a/.agent/WRITING_GUIDE.md b/.agent/WRITING_GUIDE.md index 0f4c875b..197f9d81 100644 --- a/.agent/WRITING_GUIDE.md +++ b/.agent/WRITING_GUIDE.md @@ -53,3 +53,30 @@ When describing components for security researchers: | Docker Compose native | VMM | Direct parsing, no translation | | Reproducible OS | VMM | Deterministic image builds | | Confidential GPUs | VMM + hardware | NVIDIA H100/Blackwell allocation | + +## Procedural Documentation (Guides & Tutorials) + +### Test Before You Document +- **Run every command** before documenting it — reading code is not enough +- Commands may prompt for confirmation, require undocumented env vars, or fail silently +- Create a test environment and execute the full flow end-to-end + +### Show What Success Looks Like +- **Add sample outputs** after commands so users can verify they're on track +- For deployment commands, show the key values users need to note (addresses, IDs) +- For validation commands, show both success and failure outputs + +### Environment Variables +- **List all required env vars explicitly** — don't assume users will discover them +- If multiple tools use similar-but-different var names, clarify which is which +- Show the export pattern once, then reference it in subsequent commands + +### Avoid Expert Blind Spots +- If you say "add the hash", explain how to compute the hash +- If you reference a file, explain where to find it +- If a value comes from a previous step, remind users which step + +### Cross-Reference Related Docs +- Link to prerequisite guides (don't repeat content) +- Link to detailed guides for optional deep-dives +- Use anchor links for specific sections when possible diff --git a/docs/deployment.md b/docs/deployment.md index 63ff1188..0b81f223 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -172,12 +172,23 @@ Start VMM: ### 2. Deploy KMS as CVM -> **TODO:** This section needs a proper rewrite covering auth server configuration. The current instructions are incomplete. +Production KMS requires three components running together: +- **KMS**: The key management service inside a CVM +- **auth-api**: Webhook server that validates requests against smart contracts +- **Ethereum RPC**: Access to the blockchain (via light client or external RPC) -Production KMS deployment requires: -- KMS running inside a CVM -- Auth server for authorization (webhook mode, not dev mode) -- OS image whitelisting +#### Prerequisites: Deploy DstackKms Contract + +Before deploying KMS, you need a DstackKms contract on an Ethereum-compatible network: + +```bash +cd dstack/kms/auth-eth +npm install +npx hardhat compile +PRIVATE_KEY= npx hardhat kms:deploy --with-app-impl --network +``` + +Note the deployed contract address (e.g., `0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875`). #### Deploy KMS CVM @@ -189,13 +200,29 @@ cd dstack/kms/dstack-app/ Edit the generated `.env` file: ```bash +# VMM connection VMM_RPC=unix:../../vmm-data/vmm.sock + +# Network configuration KMS_RPC_ADDR=0.0.0.0:9201 GUEST_AGENT_ADDR=127.0.0.1:9205 -GIT_REV=HEAD + +# Auth server configuration (required for production) +KMS_CONTRACT_ADDR=0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 +ETH_RPC_URL=https://rpc.phala.network + +# OS image verification OS_IMAGE=dstack-0.5.5 +IMAGE_DOWNLOAD_URL=https://github.com/Dstack-TEE/meta-dstack/releases/download/v{version}/dstack-{version}.tar.gz + +# Build reference +GIT_REV=HEAD ``` +The compose file (`docker-compose.yaml`) includes: +- **auth-api**: Connects to the smart contract via `ETH_RPC_URL` +- **kms**: Configured with webhook auth mode pointing to auth-api + Run the script again to deploy: ```bash @@ -224,25 +251,41 @@ The KMS will display its public key and TDX quote: ![KMS Bootstrap Result](assets/kms-bootstrap-result.png) -#### Configure Auth Server - -> **TODO:** Document auth server setup for production KMS. - #### Whitelist OS Image -The OS image hash must be whitelisted before apps can run. This is required for both offchain and onchain governance. +The OS image hash must be whitelisted in the DstackKms contract before apps can run: + +```bash +cd dstack/kms/auth-eth +npx hardhat kms:add-image --network 0x +``` -> **TODO:** Document OS image whitelisting for offchain auth server. +The `os_image_hash` is in the `digest.txt` file from the [meta-dstack release](https://github.com/Dstack-TEE/meta-dstack/releases). -For on-chain governance, see [On-Chain Governance](./onchain-governance.md). +For detailed smart contract operations, see [On-Chain Governance](./onchain-governance.md). --- ### 3. Deploy Gateway as CVM -> **TODO:** This section is incomplete. Gateway registration with auth server needs to be documented. +The Gateway must be registered as an app in the DstackKms contract before deployment. -Deploy the Gateway CVM: +#### Register Gateway App + +```bash +cd dstack/kms/auth-eth +npx hardhat kms:create-app --network --allow-any-device +``` + +Note the App ID from output (e.g., `0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08`). + +Set it as the gateway app in the KMS contract: + +```bash +npx hardhat kms:set-gateway --network +``` + +#### Deploy Gateway CVM ```bash cd dstack/gateway/dstack-app/ @@ -252,22 +295,47 @@ cd dstack/gateway/dstack-app/ Edit `.env`: ```bash +# VMM connection VMM_RPC=unix:../../vmm-data/vmm.sock + +# Cloudflare (for DNS-01 ACME challenge) CF_API_TOKEN=your_cloudflare_api_token + +# Domain configuration SRV_DOMAIN=example.com PUBLIC_IP=$(curl -s ifconfig.me) -ACME_STAGING=yes + +# Gateway app ID (from registration above) +GATEWAY_APP_ID=0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08 + +# Gateway URLs +MY_URL=https://gateway.example.com:9202 +BOOTNODE_URL=https://gateway.example.com:9202 + +# Other settings +ACME_STAGING=no # Set to 'yes' for testing OS_IMAGE=dstack-0.5.5 ``` -Deploy: +Run the script again - it will show the compose hash: ```bash ./deploy-to-vmm.sh -# Press 'y' to confirm +# Output: Compose hash: 0x700a50336df7c07c82457b116e144f526c29f6d8... +``` + +**Before pressing 'y'**, whitelist the compose hash: + +```bash +cd ../../kms/auth-eth +npx hardhat app:add-hash --network --app-id 0x ``` -Update `vmm.toml` with KMS and Gateway URLs: +Then return and confirm deployment. + +#### Update VMM Configuration + +After Gateway is running, update `vmm.toml` with KMS and Gateway URLs: ```toml [cvm] @@ -275,9 +343,7 @@ kms_urls = ["https://kms.example.com:9201"] gateway_urls = ["https://gateway.example.com:9202"] ``` -Restart dstack-vmm. - -> **TODO:** Document Gateway registration with offchain auth server. +Restart dstack-vmm to apply changes. --- @@ -349,9 +415,22 @@ gateway_urls = ["https://gateway.example.com:9202"] After setup, deploy apps via the VMM dashboard or CLI. -> **TODO:** Document app registration with offchain auth server. +### Register App + +Each app needs to be registered in the DstackKms contract: + +```bash +cd dstack/kms/auth-eth +npx hardhat kms:create-app --network --allow-any-device +``` + +Note the App ID from output. Then whitelist your compose hash: + +```bash +npx hardhat app:add-hash --network --app-id 0x +``` -For on-chain governed apps, see [On-Chain Governance](./onchain-governance.md). +For detailed smart contract operations, see [On-Chain Governance](./onchain-governance.md). ### Deploy via UI @@ -360,7 +439,7 @@ Open `http://localhost:9080`: ![App Deploy](assets/app-deploy.png) - Select the OS image -- Enter the App ID (from auth server registration) +- Enter the App ID (from registration above) - Upload your `docker-compose.yaml` After startup, click **Dashboard** to view: diff --git a/docs/onchain-governance.md b/docs/onchain-governance.md index 81d3a253..8ad78e38 100644 --- a/docs/onchain-governance.md +++ b/docs/onchain-governance.md @@ -14,6 +14,7 @@ On-chain governance adds: - Production dstack deployment with KMS and Gateway as CVMs (see [Deployment Guide](./deployment.md)) - Ethereum wallet with funds on Sepolia testnet (or your target network) - Node.js and npm installed +- Alchemy API key (for Sepolia) - get one at https://www.alchemy.com/ ## Deploy DstackKms Contract @@ -21,26 +22,49 @@ On-chain governance adds: cd dstack/kms/auth-eth npm install npx hardhat compile -PRIVATE_KEY= npx hardhat kms:deploy --with-app-impl --network sepolia +PRIVATE_KEY= ALCHEMY_API_KEY= npx hardhat kms:deploy --with-app-impl --network sepolia ``` -Note the deployed contract address. +The command will prompt for confirmation. Sample output: + +``` +✅ DstackApp implementation deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +DstackKms Proxy deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +Implementation deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +``` + +Note the proxy address (e.g., `0x9fE4...`). + +Set environment variables for subsequent commands: + +```bash +export KMS_CONTRACT_ADDRESS="" +export PRIVATE_KEY="" +export ALCHEMY_API_KEY="" +``` ## Configure KMS for On-Chain Auth -Update KMS to use webhook auth mode pointing to your auth-api service. +The KMS CVM includes an auth-api service that connects to your DstackKms contract. Configure it via environment variables in the KMS CVM: -> **TODO:** Document auth-api service deployment and KMS webhook configuration. +```bash +KMS_CONTRACT_ADDR= +ETH_RPC_URL= +``` + +Note: The auth-api uses `KMS_CONTRACT_ADDR`, while Hardhat tasks use `KMS_CONTRACT_ADDRESS`. + +The auth-api validates boot requests against the smart contract. See [Deployment Guide](./deployment.md#2-deploy-kms-as-cvm) for complete setup instructions. ## Whitelist OS Image ```bash -cd dstack/kms/auth-eth -npm install npx hardhat kms:add-image --network sepolia 0x ``` -The `os_image_hash` is in the `digest.txt` file from the image build. +Output: `Image added successfully` + +The `os_image_hash` is in the `digest.txt` file from the guest OS image build (see [Building Guest Images](./deployment.md#building-guest-images)). ## Register Gateway App @@ -48,7 +72,14 @@ The `os_image_hash` is in the `digest.txt` file from the image build. npx hardhat kms:create-app --network sepolia --allow-any-device ``` -Note the App ID from the output. +Sample output: + +``` +✅ App deployed and registered successfully! +Proxy Address (App Id): 0x75537828f2ce51be7289709686A69CbFDbB714F1 +``` + +Note the App ID (Proxy Address) from the output. Set it as the gateway app: @@ -56,12 +87,22 @@ Set it as the gateway app: npx hardhat kms:set-gateway --network sepolia ``` -Add the compose hash to the whitelist: +Output: `Gateway App ID set successfully` + +Add the gateway's compose hash to the whitelist. To compute the compose hash: + +```bash +sha256sum /path/to/gateway-compose.json | awk '{print "0x"$1}' +``` + +Then add it: ```bash npx hardhat app:add-hash --network sepolia --app-id ``` +Output: `Compose hash added successfully` + ## Register Apps On-Chain For each app you want to deploy: @@ -72,15 +113,25 @@ For each app you want to deploy: npx hardhat kms:create-app --network sepolia --allow-any-device ``` +Note the App ID from the output. + ### Add Compose Hash +Compute your app's compose hash: + +```bash +sha256sum /path/to/your-app-compose.json | awk '{print "0x"$1}' +``` + +Then add it: + ```bash npx hardhat app:add-hash --network sepolia --app-id ``` ### Deploy via VMM -Use the App ID when deploying through the VMM dashboard or CLI. +Use the App ID when deploying through the VMM dashboard or [VMM CLI](./vmm-cli-user-guide.md). ## Smart Contract Reference From 350917107a23929ee83ff031dc8d6ebf54d71b19 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Sat, 27 Dec 2025 11:33:09 +0000 Subject: [PATCH 03/16] feat(kms): add auth-simple config-based authorization server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add auth-simple as a production-ready alternative to auth-eth for operators who prefer config-file-based whitelisting over on-chain governance. New components: - kms/auth-simple/: Hono-based webhook server with Zod validation - kms/dstack-app/compose-simple.yaml: KMS compose for external auth - kms/dstack-app/deploy-simple.sh: Deploy script for auth-simple mode Auth server ecosystem: - auth-simple: Config-file whitelisting (production, centralized) - auth-eth: Smart contract governance (production, decentralized) - auth-mock: Always allows (dev/testing only) Documentation: - docs/deployment.md: Simplified auth-simple config for initial deploy - docs/auth-simple-operations.md: Day-to-day operations guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/auth-simple-operations.md | 298 +++++++++++++++++++ docs/deployment.md | 248 ++++++++++------ kms/auth-simple/.oxlintrc.json | 27 ++ kms/auth-simple/README.md | 204 +++++++++++++ kms/auth-simple/auth-config.example.json | 19 ++ kms/auth-simple/bun.lock | 352 +++++++++++++++++++++++ kms/auth-simple/index.test.ts | 312 ++++++++++++++++++++ kms/auth-simple/index.ts | 306 ++++++++++++++++++++ kms/auth-simple/package.json | 32 +++ kms/auth-simple/vitest.config.ts | 12 + kms/dstack-app/compose-simple.yaml | 61 ++++ kms/dstack-app/deploy-simple.sh | 141 +++++++++ 12 files changed, 1919 insertions(+), 93 deletions(-) create mode 100644 docs/auth-simple-operations.md create mode 100644 kms/auth-simple/.oxlintrc.json create mode 100644 kms/auth-simple/README.md create mode 100644 kms/auth-simple/auth-config.example.json create mode 100644 kms/auth-simple/bun.lock create mode 100644 kms/auth-simple/index.test.ts create mode 100644 kms/auth-simple/index.ts create mode 100644 kms/auth-simple/package.json create mode 100644 kms/auth-simple/vitest.config.ts create mode 100644 kms/dstack-app/compose-simple.yaml create mode 100755 kms/dstack-app/deploy-simple.sh diff --git a/docs/auth-simple-operations.md b/docs/auth-simple-operations.md new file mode 100644 index 00000000..8b253b9c --- /dev/null +++ b/docs/auth-simple-operations.md @@ -0,0 +1,298 @@ +# auth-simple Operations Guide + +This guide covers day-to-day operations for managing apps and devices with auth-simple. + +For initial deployment setup, see [Deployment Guide](./deployment.md). + +## Overview + +auth-simple uses a JSON config file to whitelist: +- **OS images** - Which guest OS versions can boot +- **KMS nodes** - Which KMS instances can onboard (mrAggregated, devices) +- **Apps** - Which applications can boot (appId, composeHash, devices) + +The config is re-read on each request, so changes take effect immediately without restart. + +--- + +## Config File Structure + +```json +{ + "osImages": ["0x..."], + "gatewayAppId": "0x...", + "kms": { + "mrAggregated": ["0x..."], + "devices": ["0x..."], + "allowAnyDevice": true + }, + "apps": { + "0x": { + "composeHashes": ["0x..."], + "devices": ["0x..."], + "allowAnyDevice": true + } + } +} +``` + +> **Note:** Only `osImages` is required. Add `gatewayAppId` after deploying the Gateway. Add `apps` entries as you deploy applications. + +--- + +## Adding an App + +### Step 1: Generate App ID + +App IDs are typically the contract address (for on-chain) or a unique identifier you choose. + +For auth-simple, you can use any unique hex string (40 characters / 20 bytes): + +```bash +# Generate a random app ID +openssl rand -hex 20 +# Output: 7a3b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b +``` + +### Step 2: Get Compose Hash + +The compose hash is computed from the normalized docker-compose file. The VMM displays it when deploying: + +``` +Docker compose file: +... +Compose hash: 0x700a50336df7c07c82457b116e144f526c29f6d8... +``` + +Or query from a running CVM: + +```bash +curl -s --unix-socket /var/run/dstack.sock http://localhost/Info | \ + jq -r '"0x" + (.tcb_info | fromjson | .compose_hash)' +``` + +> **Note:** The VMM normalizes YAML to JSON before hashing. For exact hash, use the value shown during deployment. + +### Step 3: Add to Config + +Edit your `auth-config.json`: + +```json +{ + "apps": { + "0x7a3b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b": { + "composeHashes": [ + "0x700a50336df7c07c82457b116e144f526c29f6d8..." + ], + "devices": [], + "allowAnyDevice": true + } + } +} +``` + +### Step 4: Deploy via VMM + +Use the App ID when deploying through VMM: + +```bash +./vmm-cli.py deploy \ + --app-id 7a3b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b \ + --compose docker-compose.yaml \ + ... +``` + +--- + +## Updating an App + +When you change your docker-compose.yaml, a new compose hash is generated. + +### Add New Hash (Keep Old Running) + +Add the new hash to the `composeHashes` array: + +```json +{ + "apps": { + "0x": { + "composeHashes": [ + "0x", + "0x" + ] + } + } +} +``` + +### Replace Hash (Force Upgrade) + +Remove the old hash to prevent old versions from booting: + +```json +{ + "apps": { + "0x": { + "composeHashes": [ + "0x" + ] + } + } +} +``` + +--- + +## Device Management + +Devices are identified by their TDX device ID (hardware-specific). + +### Get Device ID + +The `deviceId` is sent by the booting app/KMS in its auth request. Check auth-simple logs: + +``` +app boot auth request: { appId: '0x...', deviceId: '0x...', ... } +``` + +> **Tip:** Use `allowAnyDevice: true` initially, then restrict to specific devices after capturing IDs from logs. + +### Restrict App to Specific Devices + +```json +{ + "apps": { + "0x": { + "composeHashes": ["0x..."], + "devices": [ + "0xe5a0c70bb6503de2d31c11d85914fe3776ed5b33a078ed856327c371a60fe0fd" + ], + "allowAnyDevice": false + } + } +} +``` + +### Allow Any Device + +For initial testing or when device restriction isn't needed: + +```json +{ + "apps": { + "0x": { + "allowAnyDevice": true + } + } +} +``` + +--- + +## Removing an App + +Delete the app entry from the `apps` object: + +```json +{ + "apps": { + // "0x": { ... } <- deleted + } +} +``` + +Running instances will continue until restarted. New boot requests will be rejected. + +--- + +## Setting the Gateway App (After Gateway Deployment) + +The `gatewayAppId` field is **optional** during initial KMS deployment. Add it after deploying the Gateway. + +The Gateway is a special app that routes traffic to other apps. Once deployed, add its App ID to your config: + +```json +{ + "gatewayAppId": "0x75537828f2ce51be7289709686A69CbFDbB714F1", + "apps": { + "0x75537828f2ce51be7289709686A69CbFDbB714F1": { + "composeHashes": ["0x..."], + "allowAnyDevice": true + } + } +} +``` + +The `gatewayAppId` is returned in boot responses and used by KMS for key derivation. + +--- + +## KMS Onboarding (Multi-Node) + +To allow additional KMS nodes to onboard (receive root keys from the primary KMS), whitelist their `mrAggregated` value. + +### Get mrAggregated + +The `mrAggregated` is sent by the booting KMS in its auth request. To get this value: + +1. **From auth-simple logs**: When a KMS boots, auth-simple logs the mrAggregated: + ``` + KMS boot auth request: { osImageHash: '0x...', mrAggregated: '0x...', ... } + ``` + +2. **Initial setup**: Leave `kms.mrAggregated` empty for the first KMS (empty array allows any). After it boots, check the logs and add the value. + +### Add to Config + +```json +{ + "kms": { + "mrAggregated": ["0x"], + "allowAnyDevice": true + } +} +``` + +> **Note:** All KMS nodes using the same OS image and compose will have the same mrAggregated, so you only need to capture it once. + +--- + +## Verification + +### Check Config is Valid + +```bash +curl -s http://localhost:3001/ | jq +``` + +Returns current config status including `gatewayAppId`. + +### Test Boot Authorization + +```bash +# Test app boot +curl -s -X POST http://localhost:3001/bootAuth/app \ + -H "Content-Type: application/json" \ + -d '{ + "appId": "0x", + "composeHash": "0x", + "osImageHash": "0x", + "deviceId": "0x", + "mrAggregated": "0x...", + "instanceId": "0x...", + "tcbStatus": "UpToDate" + }' | jq +``` + +Expected responses: +- `{"isAllowed": true, ...}` - App is authorized +- `{"isAllowed": false, "reason": "app not registered", ...}` - App ID not in config +- `{"isAllowed": false, "reason": "compose hash not allowed", ...}` - Hash not whitelisted + +--- + +## See Also + +- [Deployment Guide](./deployment.md) - Initial setup +- [auth-simple README](../kms/auth-simple/README.md) - Developer reference +- [On-Chain Governance](./onchain-governance.md) - Smart contract-based alternative diff --git a/docs/deployment.md b/docs/deployment.md index 0b81f223..cfed5731 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -97,17 +97,14 @@ Start in separate terminals: For production, deploy KMS and Gateway as CVMs with hardware-rooted security. Production deployments require: - KMS running in a CVM (not on the host) -- Auth server for authorization (not dev mode) -- OS image whitelisting - -For decentralized governance via smart contracts, see [On-Chain Governance](./onchain-governance.md). +- Auth server for authorization (webhook mode) ### Production Checklist **Required:** 1. Set up TDX host with dstack-vmm -2. Deploy KMS as CVM (with auth server + OS whitelist) +2. Deploy KMS as CVM (with auth server) 3. Deploy Gateway as CVM **Optional Add-ons:** @@ -115,6 +112,7 @@ For decentralized governance via smart contracts, see [On-Chain Governance](./on 4. [Zero Trust HTTPS](#4-zero-trust-https-optional) 5. [Certificate Transparency monitoring](#5-certificate-transparency-monitoring-optional) 6. [Multi-node deployment](#6-multi-node-deployment-optional) +7. [On-chain governance](./onchain-governance.md) - Smart contract-based authorization --- @@ -135,7 +133,7 @@ cd vmm-data/ Create `vmm.toml`: ```toml -address = "unix:./vmm.sock" +address = "tcp:0.0.0.0:9080" reuse = true image_path = "./images" run_path = "./run/vm" @@ -155,12 +153,13 @@ range = [ ] [host_api] -port = 9300 +address = "vsock:2" +port = 10000 ``` Download guest images from [meta-dstack releases](https://github.com/Dstack-TEE/meta-dstack/releases) and extract to `./images/`. -> For reproducible builds and verification, see [Security Guide](./security.md). +> For reproducible builds and verification, see [Security Guide](./security-guide/security-guide.md). Start VMM: @@ -172,64 +171,98 @@ Start VMM: ### 2. Deploy KMS as CVM -Production KMS requires three components running together: +Production KMS requires: - **KMS**: The key management service inside a CVM -- **auth-api**: Webhook server that validates requests against smart contracts -- **Ethereum RPC**: Access to the blockchain (via light client or external RPC) +- **Auth server**: Webhook server that validates boot requests and returns authorization decisions + +#### Auth Server Options + +| Server | Use Case | Configuration | +|--------|----------|---------------| +| [auth-simple](../kms/auth-simple/) | Config-file-based whitelisting | JSON config file | +| [auth-eth](../kms/auth-eth/) | On-chain governance via smart contracts | Ethereum RPC + contract | +| Custom | Your own authorization logic | Implement webhook interface | + +All auth servers implement the same webhook interface: +- `GET /` - Health check +- `POST /bootAuth/app` - App boot authorization +- `POST /bootAuth/kms` - KMS boot authorization + +#### Using auth-simple (Config-Based) + +auth-simple validates boot requests against a JSON config file. + +Create `auth-config.json` for initial KMS deployment: + +```json +{ + "osImages": ["0x"], + "kms": { "allowAnyDevice": true }, + "apps": {} +} +``` + +Run auth-simple: + +```bash +cd kms/auth-simple +bun install +PORT=3001 AUTH_CONFIG_PATH=/path/to/auth-config.json bun run start +``` + +For adding Gateway, apps, and other config fields, see [auth-simple Operations Guide](./auth-simple-operations.md). + +#### Using auth-eth (On-Chain) + +For decentralized governance via smart contracts, see [On-Chain Governance](./onchain-governance.md). -#### Prerequisites: Deploy DstackKms Contract +#### Getting OS Image Hash -Before deploying KMS, you need a DstackKms contract on an Ethereum-compatible network: +The OS image hash is in the `digest.txt` file inside the guest image tarball: ```bash -cd dstack/kms/auth-eth -npm install -npx hardhat compile -PRIVATE_KEY= npx hardhat kms:deploy --with-app-impl --network +# Extract hash from release tarball +tar -xzf dstack-0.5.5.tar.gz +cat dstack-0.5.5/digest.txt +# Output: 0b327bcd642788b0517de3ff46d31ebd3847b6c64ea40bacde268bb9f1c8ec83 ``` -Note the deployed contract address (e.g., `0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875`). +Add `0x` prefix for auth-simple config: `0x0b327bcd...` #### Deploy KMS CVM +Choose the deployment script based on your auth server: + +**For auth-simple (external webhook):** + +auth-simple runs on your infrastructure, outside the CVM. + ```bash cd dstack/kms/dstack-app/ -./deploy-to-vmm.sh ``` -Edit the generated `.env` file: +Edit `.env.simple`: ```bash -# VMM connection -VMM_RPC=unix:../../vmm-data/vmm.sock - -# Network configuration +VMM_RPC=http://127.0.0.1:9080 +AUTH_WEBHOOK_URL=http://your-auth-server:3001 KMS_RPC_ADDR=0.0.0.0:9201 GUEST_AGENT_ADDR=127.0.0.1:9205 - -# Auth server configuration (required for production) -KMS_CONTRACT_ADDR=0xFE6C45aE66344CAEF5E5D7e2cbD476286D651875 -ETH_RPC_URL=https://rpc.phala.network - -# OS image verification OS_IMAGE=dstack-0.5.5 -IMAGE_DOWNLOAD_URL=https://github.com/Dstack-TEE/meta-dstack/releases/download/v{version}/dstack-{version}.tar.gz - -# Build reference -GIT_REV=HEAD +IMAGE_DOWNLOAD_URL=https://github.com/Dstack-TEE/meta-dstack/releases/download/v0.5.5/dstack-0.5.5.tar.gz ``` -The compose file (`docker-compose.yaml`) includes: -- **auth-api**: Connects to the smart contract via `ETH_RPC_URL` -- **kms**: Configured with webhook auth mode pointing to auth-api - -Run the script again to deploy: +Then run: ```bash -./deploy-to-vmm.sh +./deploy-simple.sh ``` -Monitor startup: +**For auth-eth (on-chain governance):** + +> See [On-Chain Governance Guide](./onchain-governance.md) for deploying KMS with smart contract-based authorization. + +**Monitor startup:** ```bash tail -f ../../vmm-data/run/vm//serial.log @@ -251,39 +284,17 @@ The KMS will display its public key and TDX quote: ![KMS Bootstrap Result](assets/kms-bootstrap-result.png) -#### Whitelist OS Image - -The OS image hash must be whitelisted in the DstackKms contract before apps can run: - -```bash -cd dstack/kms/auth-eth -npx hardhat kms:add-image --network 0x -``` - -The `os_image_hash` is in the `digest.txt` file from the [meta-dstack release](https://github.com/Dstack-TEE/meta-dstack/releases). - -For detailed smart contract operations, see [On-Chain Governance](./onchain-governance.md). - --- ### 3. Deploy Gateway as CVM -The Gateway must be registered as an app in the DstackKms contract before deployment. - -#### Register Gateway App +#### Prerequisites -```bash -cd dstack/kms/auth-eth -npx hardhat kms:create-app --network --allow-any-device -``` +Before deploying Gateway: +1. Register the Gateway app in your auth server config (add to `apps` section in `auth-config.json`) +2. Note the App ID you assign - you'll need it for the `.env` file -Note the App ID from output (e.g., `0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08`). - -Set it as the gateway app in the KMS contract: - -```bash -npx hardhat kms:set-gateway --network -``` +For on-chain governance, see [On-Chain Governance](./onchain-governance.md#register-gateway-app) for registration steps. #### Deploy Gateway CVM @@ -292,11 +303,11 @@ cd dstack/gateway/dstack-app/ ./deploy-to-vmm.sh ``` -Edit `.env`: +Edit `.env` with required variables: ```bash -# VMM connection -VMM_RPC=unix:../../vmm-data/vmm.sock +# VMM connection (use TCP if VMM is on same host, or remote URL) +VMM_RPC=http://127.0.0.1:9080 # Cloudflare (for DNS-01 ACME challenge) CF_API_TOKEN=your_cloudflare_api_token @@ -306,32 +317,47 @@ SRV_DOMAIN=example.com PUBLIC_IP=$(curl -s ifconfig.me) # Gateway app ID (from registration above) -GATEWAY_APP_ID=0x32467b43BFa67273FC7dDda0999Ee9A12F2AaA08 +GATEWAY_APP_ID=32467b43BFa67273FC7dDda0999Ee9A12F2AaA08 # Gateway URLs MY_URL=https://gateway.example.com:9202 BOOTNODE_URL=https://gateway.example.com:9202 -# Other settings +# WireGuard (uses same port as RPC) +WG_ADDR=0.0.0.0:9202 + +# Network settings +SUBNET_INDEX=0 ACME_STAGING=no # Set to 'yes' for testing OS_IMAGE=dstack-0.5.5 ``` -Run the script again - it will show the compose hash: +**Note on hex formats:** +- Gateway `.env` file: Use raw hex without `0x` prefix (e.g., `GATEWAY_APP_ID=32467b43...`) +- auth-simple config: Use `0x` prefix (e.g., `"0x32467b43..."`). The server normalizes both formats. + +Run the script again: ```bash ./deploy-to-vmm.sh -# Output: Compose hash: 0x700a50336df7c07c82457b116e144f526c29f6d8... ``` -**Before pressing 'y'**, whitelist the compose hash: +The script will display the compose file and compose hash, then prompt for confirmation: -```bash -cd ../../kms/auth-eth -npx hardhat app:add-hash --network --app-id 0x +``` +Docker compose file: +... +Compose hash: 0x700a50336df7c07c82457b116e144f526c29f6d8... +Configuration: +... +Continue? [y/N] ``` -Then return and confirm deployment. +**Before pressing 'y'**, add the compose hash to your auth server whitelist: +- For auth-simple: Add to `composeHashes` array in `auth-config.json` +- For auth-eth: Use `app:add-hash` (see [On-Chain Governance](./onchain-governance.md#register-gateway-app)) + +Then return to the first terminal and press 'y' to deploy. #### Update VMM Configuration @@ -354,8 +380,8 @@ Generate TLS certificates inside the TEE with automatic CAA record management. Configure in `build-config.sh`: ```bash -GATEWAY_CERT=${CERBOT_WORKDIR}/live/cert.pem -GATEWAY_KEY=${CERBOT_WORKDIR}/live/key.pem +GATEWAY_CERT=${CERTBOT_WORKDIR}/live/cert.pem +GATEWAY_KEY=${CERTBOT_WORKDIR}/live/key.pem CF_API_TOKEN= ACME_URL=https://acme-v02.api.letsencrypt.org/directory ``` @@ -396,7 +422,9 @@ The monitor runs in a loop, checking every 60 seconds. Integrate with your alert ### 6. Multi-Node Deployment (Optional) -Scale by adding VMM nodes pointing to your existing KMS and Gateway. +Scale by adding VMM nodes and KMS replicas for high availability. + +#### Adding VMM Nodes On each additional TDX host: 1. Set up dstack-vmm (see step 1) @@ -409,28 +437,62 @@ kms_urls = ["https://kms.example.com:9201"] gateway_urls = ["https://gateway.example.com:9202"] ``` ---- +#### Adding KMS Replicas (Onboarding) -## Deploying Apps +Additional KMS instances can onboard from an existing KMS to share the same root keys. This enables: +- High availability (multiple KMS nodes) +- Geographic distribution +- Load balancing -After setup, deploy apps via the VMM dashboard or CLI. +**How it works:** -### Register App +1. New KMS starts in onboard mode (empty `auto_bootstrap_domain`) +2. New KMS calls `GetTempCaCert` on source KMS +3. New KMS generates RA-TLS certificate with TDX quote +4. New KMS calls `GetKmsKey` with mTLS authentication +5. Source KMS verifies attestation via `bootAuth/kms` webhook +6. If approved, source KMS returns root keys +7. Both KMS instances now derive identical keys -Each app needs to be registered in the DstackKms contract: +**Configure new KMS for onboarding:** + +```toml +[core.onboard] +enabled = true +auto_bootstrap_domain = "" # Empty = onboard mode +quote_enabled = true # Require TDX attestation +address = "0.0.0.0" +port = 9203 # HTTP port for onboard UI +``` + +**Trigger onboard via API:** ```bash -cd dstack/kms/auth-eth -npx hardhat kms:create-app --network --allow-any-device +curl -X POST http://:9203/prpc/Onboard.Onboard?json \ + -H "Content-Type: application/json" \ + -d '{"source_url": "https://:9201/prpc", "domain": "kms2.example.com"}' ``` -Note the App ID from output. Then whitelist your compose hash: +**Finish and restart:** ```bash -npx hardhat app:add-hash --network --app-id 0x +curl http://:9203/finish +# Restart KMS - it will now serve as a full KMS with shared keys ``` -For detailed smart contract operations, see [On-Chain Governance](./onchain-governance.md). +> **Note:** For KMS onboarding with `quote_enabled = true`, add the KMS mrAggregated hash to your auth server's `kms.mrAggregated` whitelist. + +--- + +## Deploying Apps + +After setup, deploy apps via the VMM dashboard or CLI. + +### Register App + +Before deploying, register your app in your auth server: +- For auth-simple: See [auth-simple Operations Guide](./auth-simple-operations.md#adding-an-app) +- For auth-eth: See [On-Chain Governance](./onchain-governance.md#register-apps-on-chain) ### Deploy via UI diff --git a/kms/auth-simple/.oxlintrc.json b/kms/auth-simple/.oxlintrc.json new file mode 100644 index 00000000..f7d7b8e3 --- /dev/null +++ b/kms/auth-simple/.oxlintrc.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/config/schema.json", + "rules": { + "typescript": "warn", + "unicorn": "warn", + "react": "off", + "jsx-a11y": "off", + "nextjs": "off" + }, + "env": { + "node": true, + "es2022": true + }, + "globals": { + "globalThis": "readonly", + "console": "readonly" + }, + "ignore_patterns": [ + "node_modules/**", + "dist/**", + "build/**", + "coverage/**", + "*.min.js", + "*.d.ts", + "*.test.ts" + ] +} diff --git a/kms/auth-simple/README.md b/kms/auth-simple/README.md new file mode 100644 index 00000000..e13c9459 --- /dev/null +++ b/kms/auth-simple/README.md @@ -0,0 +1,204 @@ +# dstack auth-simple + +A config-based auth server for dstack KMS webhook authorization. Validates boot requests against a JSON configuration file. + +## When to Use + +| Auth Server | Use Case | +|-------------|----------| +| **auth-simple** | Production deployments with config-file-based whitelisting | +| auth-eth | Production deployments with on-chain governance | +| auth-mock | Development and testing only | + +## Installation + +```bash +bun install +``` + +## Configuration + +Create `auth-config.json` (see `auth-config.example.json`). + +For initial KMS deployment, you only need the OS image hash: + +```json +{ + "osImages": ["0x0b327bcd642788b0517de3ff46d31ebd3847b6c64ea40bacde268bb9f1c8ec83"], + "kms": { + "allowAnyDevice": true + }, + "apps": {} +} +``` + +Add more fields as you deploy Gateway and apps: + +```json +{ + "osImages": ["0x..."], + "gatewayAppId": "0x...", + "kms": { + "mrAggregated": [], + "devices": [], + "allowAnyDevice": true + }, + "apps": { + "0xYourAppId": { + "composeHashes": ["0xabc...", "0xdef..."], + "devices": [], + "allowAnyDevice": true + } + } +} +``` + +### Configuration Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `osImages` | Yes | Allowed OS image hashes (from `digest.txt`) | +| `gatewayAppId` | No | Gateway app ID (add after Gateway deployment) | +| `kms.mrAggregated` | No | Allowed KMS aggregated MR values | +| `kms.devices` | No | Allowed KMS device IDs | +| `kms.allowAnyDevice` | No | If true, skip device ID check for KMS | +| `apps..composeHashes` | No | Allowed compose hashes for this app | +| `apps..devices` | No | Allowed device IDs for this app | +| `apps..allowAnyDevice` | No | If true, skip device ID check for this app | + +### Getting Hash Values + +**OS Image Hash:** +```bash +# From meta-dstack build output +cat images/digest.txt +``` + +**Compose Hash:** +```bash +sha256sum .app-compose.json | awk '{print "0x"$1}' +``` + +## Usage + +### Development + +```bash +# Run with hot reload +bun run dev +``` + +### Production + +```bash +# Run directly +bun run start + +# Or build first +bun run build +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | 3000 | Server port | +| `AUTH_CONFIG_PATH` | ./auth-config.json | Path to config file | + +## API Endpoints + +### GET / + +Health check and server info. + +**Response:** +```json +{ + "status": "ok", + "configPath": "./auth-config.json", + "gatewayAppId": "0x..." +} +``` + +### POST /bootAuth/app + +App boot authorization. + +**Request:** +```json +{ + "mrAggregated": "0x...", + "osImageHash": "0x...", + "appId": "0x...", + "composeHash": "0x...", + "instanceId": "0x...", + "deviceId": "0x...", + "tcbStatus": "UpToDate" +} +``` + +**Response:** +```json +{ + "isAllowed": true, + "reason": "", + "gatewayAppId": "0x..." +} +``` + +### POST /bootAuth/kms + +KMS boot authorization. + +**Request:** Same as `/bootAuth/app` + +**Response:** Same as `/bootAuth/app` + +## Validation Logic + +### KMS Boot Validation + +1. `tcbStatus` must be "UpToDate" +2. `osImageHash` must be in `osImages` array +3. `mrAggregated` must be in `kms.mrAggregated` (if non-empty) +4. `deviceId` must be in `kms.devices` (unless `allowAnyDevice` is true) + +### App Boot Validation + +1. `tcbStatus` must be "UpToDate" +2. `osImageHash` must be in `osImages` array +3. `appId` must exist in `apps` object +4. `composeHash` must be in app's `composeHashes` array +5. `deviceId` must be in app's `devices` (unless `allowAnyDevice` is true) + +## Hot Reload + +The config file is re-read on every request. No restart required after config changes. + +## Integration with KMS + +Configure KMS to use webhook auth pointing to this server: + +```toml +[core.auth_api] +type = "webhook" + +[core.auth_api.webhook] +url = "http://localhost:3000" +``` + +## Testing + +```bash +# Run tests +bun run test + +# Run once +bun run test:run +``` + +## See Also + +- [auth-eth](../auth-eth/) - On-chain governance auth server +- [auth-mock](../auth-mock/) - Development/testing auth server (always allows) +- [Deployment Guide](../../docs/deployment.md) - Full deployment instructions diff --git a/kms/auth-simple/auth-config.example.json b/kms/auth-simple/auth-config.example.json new file mode 100644 index 00000000..40b3d00d --- /dev/null +++ b/kms/auth-simple/auth-config.example.json @@ -0,0 +1,19 @@ +{ + "osImages": [ + "0x0b327bcd642788b0517de3ff46d31ebd3847b6c64ea40bacde268bb9f1c8ec83" + ], + "kms": { + "mrAggregated": [], + "devices": [], + "allowAnyDevice": true + }, + "apps": { + "0xYourAppId": { + "composeHashes": [ + "0xYourComposeHash" + ], + "devices": [], + "allowAnyDevice": true + } + } +} diff --git a/kms/auth-simple/bun.lock b/kms/auth-simple/bun.lock new file mode 100644 index 00000000..acd5f8a5 --- /dev/null +++ b/kms/auth-simple/bun.lock @@ -0,0 +1,352 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "auth-simple", + "dependencies": { + "@hono/zod-validator": "0.2.2", + "hono": "4.10.3", + "zod": "3.25.76", + }, + "devDependencies": { + "@types/bun": "1.2.18", + "@types/node": "22.10.8", + "@vitest/ui": "1.6.1", + "openapi-types": "12.1.3", + "oxlint": "0.9.10", + "typescript": "5.8.3", + "vitest": "1.6.1", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "@hono/zod-validator": ["@hono/zod-validator@0.2.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-dSDxaPV70Py8wuIU2QNpoVEIOSzSXZ/6/B/h4xA7eOMz7+AarKTSGV8E6QwrdcCbBLkpqfJ4Q2TmBO0eP1tCBQ=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@0.9.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eOXKZYq5bnCSgDefgM5bzAg+4Fc//Rc4yjgKN8iDWUARweCaChiQXb6TXX8MfEfs6qayEMy6yVj0pqoFz0B1aw=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@0.9.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-UeYICDvLUaUOcY+0ugZUEmBMRLP+x8iTgL7TeY6BlpGw2ahbtUOTbyIIRWtr/0O++TnjZ+v8TzhJ9crw6Ij6dg=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@0.9.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-0Zn+vqHhrZyufFBfq9WOgiIool0gCR14BLsdS+0Dwd9o+kNxPGA5q7erQFkiC4rpkxtfBHeD3iIKMMt7d29Kyw=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@0.9.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-tkQcWpYwF42bA/uRaV2iMFePHkBjTTgomOgeEaiw6XOSJX4nBEqGIIboqqLBWT4JnKCf/L+IG3y/e1MflhKByw=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@0.9.10", "", { "os": "linux", "cpu": "x64" }, "sha512-JHbkMUnibqaSMBvLHyqTL5cWxcGW+jw+Ppt2baLISpvo34a6fBR+PI7v/A92sEDWe0W1rPhypzCwA8mKpkQ3DA=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@0.9.10", "", { "os": "linux", "cpu": "x64" }, "sha512-aBBwN7bQzidwHwEXr7BAdVvMTLWstCy5gikerjLnGDeCSXX9r+o6+yUzTOqZvOo66E+XBgOJaVbY8rsL1MLE0g=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@0.9.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-LXDnk7vKHT3IY6G1jq0O7+XMhtcHOYuxLGIx4KP+4xS6vKgBY+Bsq4xV3AtmtKlvnXkP5FxHpfLmcEtm5AWysA=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@0.9.10", "", { "os": "win32", "cpu": "x64" }, "sha512-w5XRAV4bhgwenjjpGYZGglqzG9Wv/sI+cjQWJBQsvfDXsr2w4vOBXzt1j3/Z3EcSqf4KtkCa/IIuAhQyeShUbA=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@22.10.8", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-rk+QvAEGsbX/ZPiiyel6hJHNUS9cnSbPWVaZLvE+Er3tLqQFzWMz9JOfWW7XUmKvRPfxJfbl3qYWve+RGXncFw=="], + + "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], + + "@vitest/expect": ["@vitest/expect@1.6.1", "", { "dependencies": { "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "chai": "^4.3.10" } }, "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog=="], + + "@vitest/runner": ["@vitest/runner@1.6.1", "", { "dependencies": { "@vitest/utils": "1.6.1", "p-limit": "^5.0.0", "pathe": "^1.1.1" } }, "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA=="], + + "@vitest/snapshot": ["@vitest/snapshot@1.6.1", "", { "dependencies": { "magic-string": "^0.30.5", "pathe": "^1.1.1", "pretty-format": "^29.7.0" } }, "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ=="], + + "@vitest/spy": ["@vitest/spy@1.6.1", "", { "dependencies": { "tinyspy": "^2.2.0" } }, "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw=="], + + "@vitest/ui": ["@vitest/ui@1.6.1", "", { "dependencies": { "@vitest/utils": "1.6.1", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", "pathe": "^1.1.1", "picocolors": "^1.0.0", "sirv": "^2.0.4" }, "peerDependencies": { "vitest": "1.6.1" } }, "sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg=="], + + "@vitest/utils": ["@vitest/utils@1.6.1", "", { "dependencies": { "diff-sequences": "^29.6.3", "estree-walker": "^3.0.3", "loupe": "^2.3.7", "pretty-format": "^29.7.0" } }, "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "assertion-error": ["assertion-error@1.1.0", "", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="], + + "check-error": ["check-error@1.0.3", "", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-eql": ["deep-eql@4.1.4", "", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="], + + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-func-name": ["get-func-name@2.0.2", "", {}, "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="], + + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "hono": ["hono@4.10.3", "", {}, "sha512-2LOYWUbnhdxdL8MNbNg9XZig6k+cZXm5IjHn2Aviv7honhBMOHb+jxrKIeJRZJRmn+htUCKhaicxwXuUDlchRA=="], + + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="], + + "loupe": ["loupe@2.3.7", "", { "dependencies": { "get-func-name": "^2.0.1" } }, "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "oxlint": ["oxlint@0.9.10", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.9.10", "@oxlint/darwin-x64": "0.9.10", "@oxlint/linux-arm64-gnu": "0.9.10", "@oxlint/linux-arm64-musl": "0.9.10", "@oxlint/linux-x64-gnu": "0.9.10", "@oxlint/linux-x64-musl": "0.9.10", "@oxlint/win32-arm64": "0.9.10", "@oxlint/win32-x64": "0.9.10" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-bKiiFN7Hnoaist/rditTRBXz+GXKYuLd53/NB7Q6zHB/bifELJarSoRLkAUGElIJKl4PSr3lTh1g6zehh+rX0g=="], + + "p-limit": ["p-limit@5.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@1.1.1", "", {}, "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "strip-literal": ["strip-literal@2.1.1", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinypool": ["tinypool@0.8.4", "", {}, "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ=="], + + "tinyspy": ["tinyspy@2.2.1", "", {}, "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "type-detect": ["type-detect@4.1.0", "", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node": ["vite-node@1.6.1", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", "pathe": "^1.1.1", "picocolors": "^1.0.0", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA=="], + + "vitest": ["vitest@1.6.1", "", { "dependencies": { "@vitest/expect": "1.6.1", "@vitest/runner": "1.6.1", "@vitest/snapshot": "1.6.1", "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", "local-pkg": "^0.5.0", "magic-string": "^0.30.5", "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", "tinypool": "^0.8.3", "vite": "^5.0.0", "vite-node": "1.6.1", "why-is-node-running": "^2.2.2" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "1.6.1", "@vitest/ui": "1.6.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + } +} diff --git a/kms/auth-simple/index.test.ts b/kms/auth-simple/index.test.ts new file mode 100644 index 00000000..cd931431 --- /dev/null +++ b/kms/auth-simple/index.test.ts @@ -0,0 +1,312 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { writeFileSync, unlinkSync, existsSync } from 'fs'; +import app from './index'; + +const TEST_CONFIG_PATH = './test-auth-config.json'; + +const baseBootInfo = { + mrAggregated: '0xabc123', + osImageHash: '0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a', + appId: '0xapp123', + composeHash: '0xcompose456', + instanceId: '0xinstance789', + deviceId: '0xdevice999', + tcbStatus: 'UpToDate', + advisoryIds: [], + mrSystem: '' +}; + +function writeTestConfig(config: object) { + writeFileSync(TEST_CONFIG_PATH, JSON.stringify(config, null, 2)); +} + +describe('auth-simple', () => { + beforeAll(() => { + process.env.AUTH_CONFIG_PATH = TEST_CONFIG_PATH; + }); + + afterAll(() => { + if (existsSync(TEST_CONFIG_PATH)) { + unlinkSync(TEST_CONFIG_PATH); + } + }); + + describe('GET /', () => { + it('returns health check info', async () => { + writeTestConfig({ gatewayAppId: '0xgateway' }); + + const res = await app.fetch(new Request('http://localhost/', { method: 'GET' })); + const json = await res.json(); + + expect(res.status).toBe(200); + expect(json.status).toBe('ok'); + expect(json.gatewayAppId).toBe('0xgateway'); + }); + }); + + describe('POST /bootAuth/kms', () => { + it('allows KMS boot with valid config', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + kms: { + mrAggregated: ['0xabc123'], + devices: ['0xdevice999'], + allowAnyDevice: false + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + expect(json.reason).toBe(''); + }); + + it('rejects KMS boot with invalid TCB status', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + kms: { allowAnyDevice: true } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ...baseBootInfo, tcbStatus: 'OutOfDate' }) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('TCB status'); + }); + + it('rejects KMS boot with invalid OS image', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0xdifferentimage'], + kms: { allowAnyDevice: true } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('OS image'); + }); + + it('rejects KMS boot with invalid mrAggregated', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + kms: { + mrAggregated: ['0xdifferentmr'], + allowAnyDevice: true + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('MR'); + }); + + it('allows KMS boot with allowAnyDevice', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + kms: { + mrAggregated: ['0xabc123'], + devices: ['0xotherdevice'], + allowAnyDevice: true + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + }); + }); + + describe('POST /bootAuth/app', () => { + it('allows app boot with valid config', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: { + '0xapp123': { + composeHashes: ['0xcompose456'], + devices: ['0xdevice999'], + allowAnyDevice: false + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + expect(json.reason).toBe(''); + }); + + it('rejects app boot with unregistered app', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: {} + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('not registered'); + }); + + it('rejects app boot with invalid compose hash', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: { + '0xapp123': { + composeHashes: ['0xdifferenthash'], + allowAnyDevice: true + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('compose hash'); + }); + + it('rejects app boot with invalid device', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: { + '0xapp123': { + composeHashes: ['0xcompose456'], + devices: ['0xotherdevice'], + allowAnyDevice: false + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(false); + expect(json.reason).toContain('device'); + }); + + it('allows app boot with allowAnyDevice', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: { + '0xapp123': { + composeHashes: ['0xcompose456'], + devices: [], + allowAnyDevice: true + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + }); + }); + + describe('hex normalization', () => { + it('handles uppercase hex', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['0x1FBB0CF9CC6CFBF23D6B779776FABAD2C5403D643BADB9E5E238615E4960A78A'], + apps: { + '0xAPP123': { + composeHashes: ['0xCOMPOSE456'], + allowAnyDevice: true + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + }); + + it('handles hex without 0x prefix', async () => { + writeTestConfig({ + gatewayAppId: '0xgateway', + osImages: ['1fbb0cf9cc6cfbf23d6b779776fabad2c5403d643badb9e5e238615e4960a78a'], + apps: { + 'app123': { + composeHashes: ['compose456'], + allowAnyDevice: true + } + } + }); + + const res = await app.fetch(new Request('http://localhost/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(baseBootInfo) + })); + const json = await res.json(); + + expect(json.isAllowed).toBe(true); + }); + }); +}); diff --git a/kms/auth-simple/index.ts b/kms/auth-simple/index.ts new file mode 100644 index 00000000..bda04b11 --- /dev/null +++ b/kms/auth-simple/index.ts @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { Hono } from 'hono'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; +import { readFileSync, existsSync } from 'fs'; + +// zod schemas for validation - compatible with auth-eth implementation +const BootInfoSchema = z.object({ + mrAggregated: z.string().describe('aggregated MR measurement'), + osImageHash: z.string().describe('OS Image hash'), + appId: z.string().describe('application ID'), + composeHash: z.string().describe('compose hash'), + instanceId: z.string().describe('instance ID'), + deviceId: z.string().describe('device ID'), + tcbStatus: z.string().optional().default(''), + advisoryIds: z.array(z.string()).optional().default([]), + mrSystem: z.string().optional().default('') +}); + +const BootResponseSchema = z.object({ + isAllowed: z.boolean(), + reason: z.string(), + gatewayAppId: z.string() +}); + +// config schema +const AppConfigSchema = z.object({ + composeHashes: z.array(z.string()).default([]), + devices: z.array(z.string()).default([]), + allowAnyDevice: z.boolean().default(false) +}); + +const KmsConfigSchema = z.object({ + mrAggregated: z.array(z.string()).default([]), + devices: z.array(z.string()).default([]), + allowAnyDevice: z.boolean().default(false) +}); + +const AuthConfigSchema = z.object({ + gatewayAppId: z.string().default(''), + // KMS expects these fields in the health check response + kmsContractAddr: z.string().default('0x0000000000000000000000000000000000000000'), + chainId: z.number().default(0), + appImplementation: z.string().default('0x0000000000000000000000000000000000000000'), + osImages: z.array(z.string()).default([]), + kms: KmsConfigSchema.default({}), + apps: z.record(z.string(), AppConfigSchema).default({}) +}); + +type BootInfo = z.infer; +type BootResponse = z.infer; +type AuthConfig = z.infer; + +// normalize hex string to lowercase with 0x prefix +function normalizeHex(hex: string): string { + hex = hex.toLowerCase(); + if (!hex.startsWith('0x')) { + hex = '0x' + hex; + } + return hex; +} + +// config-based backend +class ConfigBackend { + private getConfigPath(): string { + return process.env.AUTH_CONFIG_PATH || './auth-config.json'; + } + + private loadConfig(): AuthConfig { + const configPath = this.getConfigPath(); + if (!existsSync(configPath)) { + console.error(`config file not found: ${configPath}`); + return AuthConfigSchema.parse({}); + } + + try { + const content = readFileSync(configPath, 'utf-8'); + const parsed = JSON.parse(content); + return AuthConfigSchema.parse(parsed); + } catch (error) { + console.error(`failed to load config: ${error}`); + return AuthConfigSchema.parse({}); + } + } + + async checkBoot(bootInfo: BootInfo, isKms: boolean): Promise { + const config = this.loadConfig(); + const osImageHash = normalizeHex(bootInfo.osImageHash); + const deviceId = normalizeHex(bootInfo.deviceId); + + // check TCB status + if (bootInfo.tcbStatus !== 'UpToDate') { + return { + isAllowed: false, + reason: 'TCB status is not up to date', + gatewayAppId: config.gatewayAppId + }; + } + + // check OS image + const allowedOsImages = config.osImages.map(normalizeHex); + if (!allowedOsImages.includes(osImageHash)) { + return { + isAllowed: false, + reason: 'OS image is not allowed', + gatewayAppId: config.gatewayAppId + }; + } + + if (isKms) { + return this.checkKmsBoot(bootInfo, config, deviceId); + } else { + return this.checkAppBoot(bootInfo, config, deviceId); + } + } + + private checkKmsBoot(bootInfo: BootInfo, config: AuthConfig, deviceId: string): BootResponse { + const mrAggregated = normalizeHex(bootInfo.mrAggregated); + + // check aggregated MR + const allowedMrs = config.kms.mrAggregated.map(normalizeHex); + if (allowedMrs.length > 0 && !allowedMrs.includes(mrAggregated)) { + return { + isAllowed: false, + reason: 'aggregated MR not allowed', + gatewayAppId: config.gatewayAppId + }; + } + + // check device ID + if (!config.kms.allowAnyDevice) { + const allowedDevices = config.kms.devices.map(normalizeHex); + if (allowedDevices.length > 0 && !allowedDevices.includes(deviceId)) { + return { + isAllowed: false, + reason: 'KMS is not allowed to boot on this device', + gatewayAppId: config.gatewayAppId + }; + } + } + + return { + isAllowed: true, + reason: '', + gatewayAppId: config.gatewayAppId + }; + } + + private checkAppBoot(bootInfo: BootInfo, config: AuthConfig, deviceId: string): BootResponse { + const appId = normalizeHex(bootInfo.appId); + const composeHash = normalizeHex(bootInfo.composeHash); + + // check app exists + const appConfig = Object.entries(config.apps).find( + ([id]) => normalizeHex(id) === appId + )?.[1]; + + if (!appConfig) { + return { + isAllowed: false, + reason: 'app not registered', + gatewayAppId: config.gatewayAppId + }; + } + + // check compose hash + const allowedHashes = appConfig.composeHashes.map(normalizeHex); + if (!allowedHashes.includes(composeHash)) { + return { + isAllowed: false, + reason: 'compose hash not allowed', + gatewayAppId: config.gatewayAppId + }; + } + + // check device ID + if (!appConfig.allowAnyDevice) { + const allowedDevices = appConfig.devices.map(normalizeHex); + if (allowedDevices.length > 0 && !allowedDevices.includes(deviceId)) { + return { + isAllowed: false, + reason: 'app is not allowed to boot on this device', + gatewayAppId: config.gatewayAppId + }; + } + } + + return { + isAllowed: true, + reason: '', + gatewayAppId: config.gatewayAppId + }; + } + + async getGatewayAppId(): Promise { + return this.loadConfig().gatewayAppId; + } + + async getInfo(): Promise<{ + gatewayAppId: string; + kmsContractAddr: string; + chainId: number; + appImplementation: string; + }> { + const config = this.loadConfig(); + return { + gatewayAppId: config.gatewayAppId, + kmsContractAddr: config.kmsContractAddr, + chainId: config.chainId, + appImplementation: config.appImplementation, + }; + } +} + +// initialize app +const app = new Hono(); + +// initialize backend +const backend = new ConfigBackend(); + +// health check and info endpoint +app.get('/', async (c) => { + try { + const info = await backend.getInfo(); + return c.json({ + status: 'ok', + kmsContractAddr: info.kmsContractAddr, + gatewayAppId: info.gatewayAppId, + chainId: info.chainId, + appAuthImplementation: info.appImplementation, // backward compat + appImplementation: info.appImplementation, + }); + } catch (error) { + console.error('error in health check:', error); + return c.json({ + status: 'error', + message: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// app boot authentication +app.post('/bootAuth/app', + zValidator('json', BootInfoSchema), + async (c) => { + try { + const bootInfo = c.req.valid('json'); + console.log('app boot auth request:', { + appId: bootInfo.appId, + composeHash: bootInfo.composeHash, + instanceId: bootInfo.instanceId, + }); + + const result = await backend.checkBoot(bootInfo, false); + console.log('app boot auth result:', result); + return c.json(result); + } catch (error) { + console.error('error in app boot auth:', error); + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: error instanceof Error ? error.message : String(error) + }); + } + } +); + +// KMS boot authentication +app.post('/bootAuth/kms', + zValidator('json', BootInfoSchema), + async (c) => { + try { + const bootInfo = c.req.valid('json'); + console.log('KMS boot auth request:', { + osImageHash: bootInfo.osImageHash, + mrAggregated: bootInfo.mrAggregated, + instanceId: bootInfo.instanceId, + }); + + const result = await backend.checkBoot(bootInfo, true); + console.log('KMS boot auth result:', result); + return c.json(result); + } catch (error) { + console.error('error in KMS boot auth:', error); + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: error instanceof Error ? error.message : String(error) + }); + } + } +); + +// start server +const port = parseInt(process.env.PORT || '3000'); +const defaultConfigPath = process.env.AUTH_CONFIG_PATH || './auth-config.json'; +console.log(`starting auth-simple server on port ${port}`); +console.log(`config path: ${defaultConfigPath}`); + +export default { + port, + fetch: app.fetch, +}; diff --git a/kms/auth-simple/package.json b/kms/auth-simple/package.json new file mode 100644 index 00000000..d5d30ab8 --- /dev/null +++ b/kms/auth-simple/package.json @@ -0,0 +1,32 @@ +{ + "name": "auth-simple", + "version": "1.0.0", + "description": "dstack KMS config-based auth server with bun + hono + zod", + "main": "index.ts", + "scripts": { + "dev": "bun run --watch index.ts", + "start": "bun run index.ts", + "build": "bun build index.ts --outdir ./dist --target bun", + "test": "vitest", + "test:run": "vitest run", + "lint": "oxlint .", + "lint:fix": "oxlint --fix .", + "fmt": "oxlint --fix .", + "check": "bun run lint && bun run test:run" + }, + "dependencies": { + "hono": "4.10.3", + "@hono/zod-validator": "0.2.2", + "zod": "3.25.76" + }, + "devDependencies": { + "@types/bun": "1.2.18", + "@types/node": "22.10.8", + "@vitest/ui": "1.6.1", + "openapi-types": "12.1.3", + "oxlint": "0.9.10", + "typescript": "5.8.3", + "vitest": "1.6.1" + }, + "type": "module" +} diff --git a/kms/auth-simple/vitest.config.ts b/kms/auth-simple/vitest.config.ts new file mode 100644 index 00000000..17597dac --- /dev/null +++ b/kms/auth-simple/vitest.config.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}); diff --git a/kms/dstack-app/compose-simple.yaml b/kms/dstack-app/compose-simple.yaml new file mode 100644 index 00000000..f40e58d3 --- /dev/null +++ b/kms/dstack-app/compose-simple.yaml @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + +# KMS deployment with external auth-simple webhook +# auth-simple runs outside the CVM (operator infrastructure) + +services: + kms: + image: ${KMS_IMAGE} + volumes: + - kms-volume:/kms + - /var/run/dstack.sock:/var/run/dstack.sock + ports: + - 8000:8000 + restart: unless-stopped + configs: + - source: kms_config + target: /kms/kms.toml + command: sh -c 'mkdir -p /kms/certs /kms/images && exec dstack-kms -c /kms/kms.toml' + +volumes: + kms-volume: + +configs: + kms_config: + content: | + [rpc] + address = "0.0.0.0" + port = 8000 + + [rpc.tls] + key = "/kms/certs/rpc.key" + certs = "/kms/certs/rpc.crt" + + [rpc.tls.mutual] + ca_certs = "/kms/certs/tmp-ca.crt" + mandatory = false + + [core] + cert_dir = "/kms/certs" + admin_token_hash = "${ADMIN_TOKEN_HASH}" + + [core.image] + verify = true + cache_dir = "/kms/images" + download_url = "${IMAGE_DOWNLOAD_URL}" + download_timeout = "2m" + + [core.auth_api] + type = "webhook" + + [core.auth_api.webhook] + url = "${AUTH_WEBHOOK_URL}" + + [core.onboard] + enabled = true + auto_bootstrap_domain = "" + quote_enabled = true + address = "0.0.0.0" + port = 8000 diff --git a/kms/dstack-app/deploy-simple.sh b/kms/dstack-app/deploy-simple.sh new file mode 100755 index 00000000..f027f24a --- /dev/null +++ b/kms/dstack-app/deploy-simple.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: © 2025 Phala Network +# +# SPDX-License-Identifier: Apache-2.0 + +# Deploy KMS with external auth-simple webhook +# auth-simple runs outside the CVM (operator infrastructure) + +set -e + +# Check if .env.simple exists +if [ -f ".env.simple" ]; then + echo "Loading environment variables from .env.simple file..." + set -a + source .env.simple + set +a +else + # Create a template .env.simple file + echo "Creating template .env.simple file..." + cat >.env.simple < Date: Sun, 28 Dec 2025 09:00:16 +0000 Subject: [PATCH 04/16] fix(sdk/rust): add Host header for Unix socket requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The http-client-unix-domain-socket library requires a Host header for HTTP/1.1 requests over Unix sockets. Without it, requests return 400 Bad Request. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- sdk/rust/src/dstack_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/rust/src/dstack_client.rs b/sdk/rust/src/dstack_client.rs index 40a5242a..f1f206a1 100644 --- a/sdk/rust/src/dstack_client.rs +++ b/sdk/rust/src/dstack_client.rs @@ -104,7 +104,7 @@ impl DstackClient { .send_request_json::<_, _, Value>( path, Method::POST, - &[("Content-Type", "application/json")], + &[("Content-Type", "application/json"), ("Host", "dstack")], Some(&payload), ) .await?; From 4ce0571b6e9357ebc53f6d6846de256e34ed4dbf Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Mon, 29 Dec 2025 06:33:49 +0000 Subject: [PATCH 05/16] docs: revamp SDK READMEs and update main documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SDK Documentation: - Rewrite all SDK READMEs (Python, JS, Rust, Go) for concise style - Add HTTP API reference section to sdk/README.md - Fix curl API docs (invalid JSON, mark Sign/Verify as unreleased) - Test all SDK code snippets against live dstack.sock Main README: - Add chat.redpill.ai as "Try it now" entry point - Add HTTP API link in SDKs section Security Documentation: - Split security.md into security-model.md and security-best-practices.md - Update AI docs (inference, agents, training) for TEE-agnostic language 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 12 +- docs/agents.md | 10 +- docs/cvm-boundaries.md | 2 +- docs/inference.md | 10 +- docs/onchain-governance.md | 2 +- ...security.md => security-best-practices.md} | 0 docs/security-model.md | 131 +++ docs/training.md | 6 +- sdk/README.md | 24 +- sdk/curl/api.md | 9 +- sdk/go/README.md | 250 ++-- sdk/js/README.md | 1039 +++-------------- sdk/js/package.json | 14 +- sdk/python/README.md | 883 +++----------- sdk/rust/README.md | 282 +++-- 15 files changed, 846 insertions(+), 1828 deletions(-) rename docs/{security.md => security-best-practices.md} (100%) create mode 100644 docs/security-model.md diff --git a/README.md b/README.md index 5399e39f..83830cf1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ AI providers ask users to trust them with sensitive data. But trust doesn't scal ## Getting Started -**1. Deploy a private LLM:** +**Try it now:** Chat with LLMs running in TEE at [chat.redpill.ai](https://chat.redpill.ai). Click the shield icon to verify attestations from Intel TDX and NVIDIA GPUs. + +**Deploy your own:** ```yaml # docker-compose.yaml @@ -55,12 +57,6 @@ services: Deploy to any TDX host with the `dstack-nvidia-0.5.x` base image, or use [Phala Cloud](https://cloud.phala.network) for managed infrastructure. -**2. Verify what's running:** - -Every dstack deployment can generate a TDX attestation quote: cryptographic proof of the exact code running inside the confidential VM. - -Try it: [dstack-demo.phala.com](https://dstack-demo.phala.com) → Click "Verify" → See the proof on [proof.t16z.com](https://proof.t16z.com) - Want to deploy a self hosted dstack? Check our [full deployment guide →](./docs/deployment.md) ## Architecture @@ -83,6 +79,8 @@ Your container runs inside a Confidential VM (Intel TDX) with optional GPU isola ## SDKs +Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use the [HTTP API](./sdk/curl/api.md) directly with curl, or use a language SDK: + | Language | Install | Docs | |----------|---------|------| | Python | `pip install dstack-sdk` | [README](./sdk/python/README.md) | diff --git a/docs/agents.md b/docs/agents.md index 484f7e0a..2e52f977 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -15,7 +15,7 @@ Running in a TEE ensures these secrets remain protected, and attestation proves ``` ┌────────────────────────────────────────────────────────────┐ -│ dstack CVM (TDX) │ +│ dstack CVM │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Your Agent │ │ @@ -244,8 +244,8 @@ attestation = requests.get( params={"nonce": "random-challenge"} ).json() -# Verify TDX quote via proof.t16z.com or programmatically -is_valid = verify_tdx_quote(attestation['quote']) +# Verify attestation quote via proof.t16z.com or programmatically +is_valid = verify_quote(attestation['quote']) # Check agent code hash matches expected assert attestation['compose_hash'] == EXPECTED_AGENT_HASH @@ -257,9 +257,9 @@ assert attestation['compose_hash'] == EXPECTED_AGENT_HASH |---------|------------| | API Keys | Encrypted at deploy, decrypted only in TEE | | Wallet Keys | Derived deterministically, never leave TEE | -| Agent Code | Measured and attested via TDX | +| Agent Code | Measured and attested via TEE | | Data in Transit | TLS termination inside TEE | -| Execution Proof | TDX quotes for any operation | +| Execution Proof | Attestation quotes for any operation | ## Production Considerations diff --git a/docs/cvm-boundaries.md b/docs/cvm-boundaries.md index fa5ffbdc..975fd278 100644 --- a/docs/cvm-boundaries.md +++ b/docs/cvm-boundaries.md @@ -117,7 +117,7 @@ dstack uses encrypted environment variables to allow app developers to securely - CVM performs basic regex validation on values - Final result is stored as /dstack/.hostshared/.decrypted-env and loaded system-wide via app-compose.service -This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security.md](security.md) for more details. +This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security-best-practices.md](security-best-practices.md) for more details. ### .user-config This is an optional application-specific configuration file that applications inside the CVM can access. dstack OS simply stores it at /dstack/user-config without any measurement or additional processing. diff --git a/docs/inference.md b/docs/inference.md index 7153997e..8933205e 100644 --- a/docs/inference.md +++ b/docs/inference.md @@ -6,19 +6,19 @@ Run LLM inference with hardware attestation and cryptographic proof of responses ``` ┌─────────────┐ ┌──────────────────────────────────────────────┐ -│ Client │ │ dstack CVM (TDX) │ +│ Client │ │ dstack CVM │ │ │ │ ┌─────────────┐ ┌─────────────────┐ │ │ Request ───┼────►│ │ vllm-proxy │──────│ vLLM Backend │ │ │ │ │ │ (attestation│ │ (OpenAI API) │ │ │ ◄──────────┼─────│ │ + signing) │◄─────│ │ │ │ Response │ │ └─────────────┘ └─────────────────┘ │ │ + Signature│ │ │ │ -│ + TDX Quote│ │ └── /var/run/dstack.sock │ +│ + TEE Quote│ │ └── /var/run/dstack.sock │ └─────────────┘ └──────────────────────────────────────────────┘ ``` **vllm-proxy** adds a security layer to vLLM: -- **Hardware attestation** — TDX quotes proving execution in secure hardware +- **Hardware attestation** — TEE quotes proving execution in secure hardware - **Response signing** — Every response cryptographically signed (ECDSA + ED25519) - **GPU attestation** — NVIDIA Confidential Computing verification (when available) @@ -87,14 +87,14 @@ attestation = requests.get( params={"nonce": "your-random-nonce"} ).json() -print(f"TDX quote: {attestation['tdx_quote'][:100]}...") +print(f"TEE quote: {attestation['quote'][:100]}...") print(f"GPU evidence: {attestation.get('gpu_evidence', 'N/A')}") ``` Or paste the attestation into [proof.t16z.com](https://proof.t16z.com) for visual verification. The attestation binds the signing key to the TEE hardware. Verify: -1. TDX quote is valid (via [proof.t16z.com](https://proof.t16z.com) or dstack verifier) +1. TEE quote is valid (via [proof.t16z.com](https://proof.t16z.com) or dstack verifier) 2. Signing address in quote matches the one that signed responses 3. Response hash matches your received content diff --git a/docs/onchain-governance.md b/docs/onchain-governance.md index 8ad78e38..d76fb113 100644 --- a/docs/onchain-governance.md +++ b/docs/onchain-governance.md @@ -186,4 +186,4 @@ Source: [`kms/auth-eth/contracts/`](../kms/auth-eth/contracts/) ## See Also - [Deployment Guide](./deployment.md) - Setting up dstack infrastructure -- [Security Guide](./security.md) - Security best practices +- [Security Best Practices](./security-best-practices.md) diff --git a/docs/security.md b/docs/security-best-practices.md similarity index 100% rename from docs/security.md rename to docs/security-best-practices.md diff --git a/docs/security-model.md b/docs/security-model.md new file mode 100644 index 00000000..93a8e955 --- /dev/null +++ b/docs/security-model.md @@ -0,0 +1,131 @@ +# dstack Security Model + +dstack protects your code and data from infrastructure operators. Using TEE hardware isolation, your workloads run in encrypted memory that the host cannot read or modify. You can cryptographically verify that your exact code runs in genuine TEE hardware. + +This document helps you evaluate whether dstack's security model fits your needs. + +## Trust Boundaries + +dstack removes the need to trust infrastructure operators. The cloud provider cannot read your memory, modify your code, or access your secrets. Network attackers cannot intercept your traffic because TLS terminates inside the TEE with keys fully controlled by the TEE (Zero Trust HTTPS). Docker registries cannot serve malicious images because the TEE verifies SHA256 digests before pulling. + +The only thing you must trust is **TEE hardware** (currently Intel TDX, with AMD SEV support planned). You trust that the TEE provides genuine memory encryption and that quotes are signed by real hardware. For GPU workloads, you also trust **NVIDIA GPU hardware** and NVIDIA's Remote Attestation Service (NRAS). These are hardware-level trust assumptions. + +Everything else is verifiable. + +**The dstack OS** is measured during boot and recorded in the attestation quote. You verify it by rebuilding from [meta-dstack](https://github.com/Dstack-TEE/meta-dstack) source and comparing measurements, or by checking that the OS hash is whitelisted in a governance contract you trust. + +**The KMS** runs in its own TEE with its own attestation quote. You verify it the same way you verify any dstack workload. + +### What dstack Cannot Protect + +TEE technology has inherent limitations. Side-channel attacks against TEE hardware are researched actively, and microarchitectural vulnerabilities are discovered periodically. Hardware vendors release TCB updates to address these, so keep your TCB version current. + +dstack protects the execution environment, not your application code. Bugs in your application remain exploitable. Secrets that you log or transmit insecurely can still leak. Your code must follow secure development practices. + +Infrastructure operators can still deny service. They can shut down your workload, throttle resources, or block network access. If availability matters, plan for redundancy across providers. + +## Security Guarantees + +### Confidentiality + +| Layer | Protection | Mechanism | +|-------|------------|-----------| +| Memory | Encrypted at runtime | TEE hardware encryption | +| Disk | Encrypted at rest | Per-app keys from KMS (AES-256-GCM) | +| Environment | Encrypted in transit | X25519 ECDH + AES-256-GCM | +| Network | Encrypted end-to-end | Zero Trust HTTPS (TLS terminates in TEE) | + +### Integrity + +| Component | Verification | Measurement | +|-----------|--------------|-------------| +| Hardware | TEE signature | Attestation quote | +| Firmware | Boot measurement | MRTD | +| OS | Boot measurement | RTMR0-2 | +| Application | Runtime measurement | RTMR3 (compose-hash) | + +### Isolation + +Each application derives unique keys from the KMS based on its identity. Instance-level secrets use the instance ID to create unique disk encryption keys. No keys are shared between different applications. + +## GPU Security for AI Workloads + +dstack supports NVIDIA H100, H200, and B200 GPUs in confidential compute mode for AI inference and training workloads. + +### How It Works + +GPUs are passed through via VFIO directly to the TEE-protected CVM. The GPU operates in confidential compute mode, encrypting data during computation. Both the CPU TEE and NVIDIA GPU provide hardware isolation together. If either component fails verification, the security model breaks. + +### Dual Attestation + +GPU workloads require verification of both hardware components. The CPU TEE provides the quote that verifies CPU and memory isolation. NVIDIA's Remote Attestation Service (NRAS) independently verifies the GPU is genuine and running in confidential mode. Both attestations must pass for complete verification. + +### AI Workload Protection + +Models and training data stay within the hardware-protected environment. The infrastructure operator cannot access model weights, training data, or inference inputs/outputs. Response integrity is provable through cryptographic signatures generated inside the TEE. Performance overhead is minimal, achieving approximately 99% efficiency compared to native execution. + +## Chain of Trust + +dstack implements layered verification from hardware to application. Each layer is measured and included in the attestation quote, which TEE hardware cryptographically signs. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Attestation Quote (signed by TEE hardware) │ +│ ├── Hardware: TEE signature proves genuine hardware │ +│ ├── MRTD: Virtual firmware measurement │ +│ ├── RTMR0-2: OS kernel and boot parameters │ +│ ├── RTMR3: Application (compose-hash) + KMS binding │ +│ └── reportData: Your challenge (replay protection) │ +├─────────────────────────────────────────────────────────────────┤ +│ Event Log (RTMR3 breakdown) │ +│ ├── compose-hash: SHA256 of your docker-compose │ +│ ├── key-provider: KMS root CA public key hash │ +│ └── instance-id: Unique per deployment │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Hardware layer.** The TEE provides the root of trust. The attestation quote is cryptographically signed by TEE hardware, and verification confirms the signature chain. The TCB status shows whether firmware is patched against known vulnerabilities. + +**OS layer.** The dstack OS is measured during boot into MRTD and RTMR0-2. MRTD captures the virtual firmware. RTMR0 captures firmware configuration. RTMR1 captures the Linux kernel. RTMR2 captures kernel command-line parameters. You verify integrity by computing expected measurements from meta-dstack source and comparing them to the quote. + +**Application layer.** Your application is measured into RTMR3 as the compose-hash, which is the SHA256 hash of your normalized docker-compose configuration. Each image must use SHA256 digest pinning. This proves exactly which container images are running and that no code substitution happened after measurement. + +**Key management layer.** The KMS root CA public key hash is recorded in RTMR3 as the key-provider event. This binds your workload to a specific KMS instance. The KMS itself runs in a TEE with its own attestation quote, so you can verify the KMS the same way you verify any workload. + +## Verification Checklist + +Use this checklist to verify a workload running in a dstack CVM. + +**Platform verification:** +- [ ] Attestation quote signature is valid +- [ ] TCB status is up-to-date (no unpatched vulnerabilities) +- [ ] OS measurements match expected values (MRTD, RTMR0-2) +- [ ] OS image hash is whitelisted (if using governance) + +**Application verification:** +- [ ] compose-hash matches your docker-compose +- [ ] All images use SHA256 digests (no mutable tags) +- [ ] RTMR3 event log replays correctly +- [ ] reportData contains your challenge (replay protection) + +**Key management verification:** +- [ ] key-provider matches expected KMS identity +- [ ] KMS attestation is valid + +## Limitations + +### Attestation proves identity, not correctness + +Attestation proves which code is running, not that the code is bug-free. It proves the environment is isolated, not that your application handles secrets correctly. You still need to audit your application code and follow secure development practices. + +### Environment variables need application-layer authentication + +Encrypted environment variables prevent the host from reading your secrets. However, the host can replace encrypted values with different ones. Your application should verify authenticity using patterns like LAUNCH_TOKEN. See [security-best-practices.md](security-best-practices.md) for details. + +### KMS root key security + +All keys derive from the KMS root key, which is protected by TEE isolation. Like all TEE-based systems, a TEE compromise could expose the root key. We are developing MPC-based KMS where the root key is distributed across multiple parties, eliminating this single point of failure. + +## Further Reading + +For production deployment guidance, see [security-best-practices.md](security-best-practices.md). For smart contract authorization details, see [onchain-governance.md](onchain-governance.md). For technical details about CVM boundaries and APIs, see [cvm-boundaries.md](cvm-boundaries.md). diff --git a/docs/training.md b/docs/training.md index 340b2ea1..5c9d1ec8 100644 --- a/docs/training.md +++ b/docs/training.md @@ -19,7 +19,7 @@ Running training in a TEE ensures: ``` ┌──────────────────────────────────────────────────────────────┐ -│ dstack CVM (TDX) │ +│ dstack CVM │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Training Pipeline │ │ @@ -282,8 +282,8 @@ With CC-enabled GPUs: | Stage | Protection | |-------|------------| | Dataset at rest | Encrypted storage | -| Dataset in memory | TDX memory encryption | -| Model weights | TDX + GPU CC memory encryption | +| Dataset in memory | TEE memory encryption | +| Model weights | TEE + GPU CC memory encryption | | Gradients | Never leave TEE | | Output model | Attestation-signed | diff --git a/sdk/README.md b/sdk/README.md index 9a21fd31..930eeba0 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,3 +1,23 @@ -# SDK +# dstack SDKs -This SDK propose for simple the interaction with dstack guest agent. You can also download simulator [here](https://github.com/Leechael/dstack-simulator/releases) or using the [Docker based image](https://hub.docker.com/r/phalanetwork/dstack-simulator) to kick start without real TDX hardware. +Client libraries for interacting with the dstack guest agent from inside a TEE. + +## HTTP API + +All SDKs communicate with the guest agent via HTTP over a Unix socket (`/var/run/dstack.sock`). See the [HTTP API Reference](curl/api.md) for direct access using curl or any HTTP client. + +## SDKs + +| Language | Path | +|----------|------| +| [Python](python/) | `sdk/python` | +| [JavaScript/TypeScript](js/) | `sdk/js` | +| [Rust](rust/) | `sdk/rust` | +| [Go](go/) | `sdk/go` | + +## Simulator + +For local development without TDX hardware, use the simulator: + +- [Download releases](https://github.com/Leechael/dstack-simulator/releases) +- [Docker image](https://hub.docker.com/r/phalanetwork/dstack-simulator) diff --git a/sdk/curl/api.md b/sdk/curl/api.md index 2a18c393..50b59151 100644 --- a/sdk/curl/api.md +++ b/sdk/curl/api.md @@ -81,7 +81,7 @@ curl --unix-socket /var/run/dstack.sock -X POST \ -d '{ "path": "my/key/path", "purpose": "signing", - "algorithm": "ed25519", + "algorithm": "ed25519" }' ``` @@ -193,7 +193,7 @@ curl --unix-socket /var/run/dstack.sock -X POST \ **Response:** Empty response with HTTP 200 status code on success. -### 6. Sign +### 6. Sign (not yet released) Signs a payload. @@ -230,7 +230,7 @@ curl --unix-socket /var/run/dstack.sock -X POST \ } ``` -### 7. Verify +### 7. Verify (not yet released) Verifies a signature. @@ -265,8 +265,6 @@ curl --unix-socket /var/run/dstack.sock -X POST \ } ``` -``` - ## Error Responses All endpoints may return the following HTTP status codes: @@ -280,3 +278,4 @@ Error responses will include a JSON body with error details: { "error": "Error description" } +``` diff --git a/sdk/go/README.md b/sdk/go/README.md index 4379d2e6..03024f1b 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -1,6 +1,6 @@ -# dstack SDK +# dstack SDK for Go -The dstack SDK for Go. +Access TEE features from your Go application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security. ## Installation @@ -8,7 +8,7 @@ The dstack SDK for Go. go get github.com/Dstack-TEE/dstack/sdk/go ``` -## Basic Usage +## Quick Start ```go package main @@ -16,111 +16,213 @@ package main import ( "context" "fmt" - "log/slog" "github.com/Dstack-TEE/dstack/sdk/go/dstack" ) func main() { - client := dstack.NewDstackClient( - // dstack.WithEndpoint("http://localhost"), - // dstack.WithLogger(slog.Default()), - ) - - // Get information about the dstack client instance - info, err := client.Info(context.Background()) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(info.AppID) // Application ID - fmt.Println(info.TcbInfo.Mrtd) // Access TCB info directly - fmt.Println(info.TcbInfo.EventLog[0].Event) // Access event log entries - - path := "/test" - purpose := "test" // or leave empty - - // Derive a key with optional path and purpose - deriveKeyResp, err := client.GetKey(context.Background(), path, purpose) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(deriveKeyResp.Key) - - // Generate TDX quote - tdxQuoteResp, err := client.GetQuote(context.Background(), []byte("test")) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(tdxQuoteResp.Quote) // 0x0000000000000000000 ... - - rtmrs, err := tdxQuoteResp.ReplayRTMRs() - if err != nil { - fmt.Println(err) - return - } - fmt.Println(rtmrs) // map[0:00000000000000000 ... + client := dstack.NewDstackClient() + + // Derive a deterministic key for your wallet + key, _ := client.GetKey(context.Background(), "wallet/eth", "", "secp256k1") + fmt.Println(key.Key) // Same path always returns the same key + + // Generate an attestation quote + quote, _ := client.GetQuote(context.Background(), []byte("my-app-state")) + fmt.Println(quote.Quote) } ``` -## API Reference +The client automatically connects to `/var/run/dstack.sock`. For local development with the simulator: -### DstackClient +```go +client := dstack.NewDstackClient(dstack.WithEndpoint("http://localhost:8090")) +``` -#### Constructor +## Core API + +### Derive Keys + +`GetKey()` derives deterministic keys bound to your application's identity (`app_id`). The same path always produces the same key for your app, but different apps get different keys even with the same path. ```go -func NewDstackClient(opts ...DstackClientOption) *DstackClient +// Derive keys by path +ethKey, _ := client.GetKey(ctx, "wallet/ethereum", "", "secp256k1") +btcKey, _ := client.GetKey(ctx, "wallet/bitcoin", "", "secp256k1") + +// Use path to separate keys +mainnetKey, _ := client.GetKey(ctx, "wallet/eth/mainnet", "", "secp256k1") +testnetKey, _ := client.GetKey(ctx, "wallet/eth/testnet", "", "secp256k1") + +// Different algorithm +edKey, _ := client.GetKey(ctx, "signing/key", "", "ed25519") ``` -Options: -- `WithEndpoint(endpoint string)`: Sets the endpoint (Unix socket path or HTTP(S) URL). Defaults to '/var/run/dstack.sock'. -- `WithLogger(logger *slog.Logger)`: Sets the logger. Defaults to `slog.Default()`. +**Parameters:** +- `path`: Key derivation path (determines the key) +- `purpose`: Included in signature chain message, does not affect the derived key +- `algorithm`: `"secp256k1"` or `"ed25519"` + +**Returns:** `*GetKeyResponse` +- `Key`: Hex-encoded private key +- `SignatureChain`: Signatures proving the key was derived in a genuine TEE + +### Generate Attestation Quotes -The client uses `DSTACK_SIMULATOR_ENDPOINT` environment variable if set. +`GetQuote()` creates a TDX quote proving your code runs in a genuine TEE. -NOTE: Leave endpoint empty in production. You only need to add `volumes` in your docker-compose file: +```go +quote, _ := client.GetQuote(ctx, []byte("user:alice:nonce123")) -```yaml - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock +// Replay RTMRs from the event log +rtmrs, _ := quote.ReplayRTMRs() +fmt.Println(rtmrs) ``` -#### Methods +**Parameters:** +- `reportData`: Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256). -- `Info(ctx context.Context) (*InfoResponse, error)`: Retrieves information about the CVM instance. -- `GetKey(ctx context.Context, path string, purpose string, algorithm string) (*GetKeyResponse, error)`: Derives a key for the given path, purpose and algorithm. -- `GetQuote(ctx context.Context, reportData []byte) (*GetQuoteResponse, error)`: Generates a TDX quote using SHA512 as the hash algorithm. -- `GetTlsKey(ctx context.Context, path string, subject string, altNames []string, usageRaTls bool, usageServerAuth bool, usageClientAuth bool, randomSeed bool) (*GetTlsKeyResponse, error)`: Derives a key for the given path and purpose. -- `Sign(ctx context.Context, algorithm string, data []byte) (*SignResponse, error)`: Signs a payload -- `Verify(ctx context.Context, algorithm string, data []byte, signature []byte, public_key []byte) (*VerifyResponse, error)`: Verifies a payload +**Returns:** `*GetQuoteResponse` +- `Quote`: TDX quote as bytes +- `EventLog`: JSON string of measured events +- `ReplayRTMRs()`: Method to compute RTMR values from event log -## Development +### Get Instance Info + +```go +info, _ := client.Info(ctx) +fmt.Println(info.AppID) +fmt.Println(info.InstanceID) +fmt.Println(info.TcbInfo) + +// Decode TCB info for detailed measurements +tcb, _ := info.DecodeTcbInfo() +fmt.Println(tcb.Mrtd) +``` + +**Returns:** `*InfoResponse` +- `AppID`: Application identifier +- `InstanceID`: Instance identifier +- `AppName`: Application name +- `TcbInfo`: TCB measurements (JSON string) +- `ComposeHash`: Hash of the app configuration +- `AppCert`: Application certificate (PEM) +- `DecodeTcbInfo()`: Helper method to parse TcbInfo JSON + +### Generate TLS Certificates + +`GetTlsKey()` creates fresh TLS certificates. Unlike `GetKey()`, each call generates a new random key. + +```go +tls, _ := client.GetTlsKey( + ctx, + dstack.WithSubject("api.example.com"), + dstack.WithAltNames([]string{"localhost"}), + dstack.WithUsageRaTls(true), // Embed attestation in certificate + dstack.WithUsageServerAuth(true), +) + +fmt.Println(tls.Key) // PEM private key +fmt.Println(tls.CertificateChain) // Certificate chain +``` + +**Options:** +- `WithSubject(subject)`: Certificate common name (e.g., domain name) +- `WithAltNames(altNames)`: List of subject alternative names +- `WithUsageRaTls(bool)`: Embed TDX quote in certificate extension +- `WithUsageServerAuth(bool)`: Enable for server authentication +- `WithUsageClientAuth(bool)`: Enable for client authentication + +**Returns:** `*GetTlsKeyResponse` +- `Key`: PEM-encoded private key +- `CertificateChain`: List of PEM certificates + +### Sign and Verify -Set up [Go](https://go.dev/doc/install). +Sign data using TEE-derived keys (not yet released): -### Running the Simulator +```go +result, _ := client.Sign(ctx, "ed25519", []byte("message to sign")) +fmt.Println(result.Signature) +fmt.Println(result.PublicKey) + +// Verify the signature +valid, _ := client.Verify(ctx, "ed25519", []byte("message to sign"), result.Signature, result.PublicKey) +fmt.Println(valid.Valid) // true +``` + +**`Sign()` Parameters:** +- `algorithm`: `"ed25519"`, `"secp256k1"`, or `"secp256k1_prehashed"` +- `data`: Data to sign + +**`Sign()` Returns:** `*SignResponse` +- `Signature`: Signature bytes +- `PublicKey`: Public key bytes +- `SignatureChain`: Signatures proving TEE origin + +**`Verify()` Parameters:** +- `algorithm`: Algorithm used for signing +- `data`: Original data +- `signature`: Signature to verify +- `publicKey`: Public key to verify against + +**`Verify()` Returns:** `*VerifyResponse` +- `Valid`: Boolean indicating if signature is valid + +### Emit Events + +Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record. + +```go +client.EmitEvent(ctx, "config_loaded", []byte("production")) +client.EmitEvent(ctx, "plugin_initialized", []byte("auth-v2")) +``` + +**Parameters:** +- `event`: Event name (string identifier) +- `payload`: Event value (bytes) -For local development without TDX devices, you can use the simulator under `sdk/simulator`. +## Development -Run the simulator with: +For local development without TDX hardware, use the simulator: ```bash -cd sdk/simulator +git clone https://github.com/Dstack-TEE/dstack.git +cd dstack/sdk/simulator ./build.sh ./dstack-simulator ``` -### Running Tests +Then set the endpoint: + ```bash -DSTACK_SIMULATOR_ENDPOINT=$(realpath ../simulator/dstack.sock) go test -v ./dstack +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 +``` + +Run tests: -# or for the old Tappd client -DSTACK_SIMULATOR_ENDPOINT=$(realpath ../simulator/tappd.sock) go test -v ./tappd +```bash +go test -v ./dstack ``` +--- + +## Migration from TappdClient + +Replace `tappd` package with `dstack`: + +```go +// Before +import "github.com/Dstack-TEE/dstack/sdk/go/tappd" +client := tappd.NewTappdClient() + +// After +import "github.com/Dstack-TEE/dstack/sdk/go/dstack" +client := dstack.NewDstackClient() +``` + +Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` + ## License -Apache License +Apache License 2.0 diff --git a/sdk/js/README.md b/sdk/js/README.md index 7d6da3af..66bc149b 100644 --- a/sdk/js/README.md +++ b/sdk/js/README.md @@ -1,6 +1,6 @@ -# dstack SDK +# dstack SDK for JavaScript/TypeScript -The dstack SDK provides a JavaScript/TypeScript client for secure communication with the dstack Trusted Execution Environment (TEE). This SDK enables applications to derive cryptographic keys, generate remote attestation quotes, and perform other security-critical operations within confidential computing environments. +Access TEE features from your JavaScript/TypeScript application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security. ## Installation @@ -8,999 +8,262 @@ The dstack SDK provides a JavaScript/TypeScript client for secure communication npm install @phala/dstack-sdk ``` -## Overview - -The dstack SDK enables secure communication with dstack Trusted Execution Environment (TEE) instances. dstack applications are defined using `app-compose.json` (based on the `AppCompose` structure) and deployed as containerized applications using Docker Compose. - -### Application Architecture - -dstack applications consist of: -- **App Configuration**: `app-compose.json` defining app metadata, security settings, and Docker Compose content -- **Container Deployment**: Docker Compose configuration embedded within the app definition -- **TEE Integration**: Access to TEE functionality via Unix socket (`/var/run/dstack.sock`) - -### SDK Capabilities - -- **Key Derivation**: Deterministic secp256k1 key generation for blockchain and Web3 applications -- **Remote Attestation**: TDX quote generation providing cryptographic proof of execution environment -- **TLS Certificate Management**: Fresh certificate generation with optional RA-TLS support for secure connections -- **Deployment Security**: Client-side encryption of sensitive environment variables ensuring secrets are only accessible to target TEE applications -- **Blockchain Integration**: Ready-to-use adapters for Ethereum (Viem) and Solana ecosystems - -### Socket Connection Requirements - -To use the SDK, your Docker Compose configuration must bind-mount the dstack socket: - -```yaml -# docker-compose.yml -services: - your-app: - image: your-app-image - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock # dstack OS 0.5.x - # For dstack OS 0.3.x compatibility (deprecated): - # - /var/run/tappd.sock:/var/run/tappd.sock -``` - -## Basic Usage - -### Application Setup - -First, ensure your dstack application is properly configured: - -**1. App Configuration (`app-compose.json`)** -```json -{ - "manifest_version": 1, - "name": "my-secure-app", - "runner": "docker-compose", - "docker_compose_file": "services:\n app:\n build: .\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - NODE_ENV=production", - "public_tcbinfo": true, - "kms_enabled": false, - "gateway_enabled": false -} -``` - -**Note**: The `docker_compose_file` field contains the actual Docker Compose YAML content as a string, not a file path. - -### SDK Integration +## Quick Start ```typescript import { DstackClient } from '@phala/dstack-sdk'; -// Create client - automatically connects to /var/run/dstack.sock const client = new DstackClient(); -// For local development with simulator -const devClient = new DstackClient('http://localhost:8090'); +// Derive a deterministic key for your wallet +const key = await client.getKey('wallet/eth'); +console.log(key.key); // Same path always returns the same key -// Get TEE instance information -const info = await client.info(); -console.log('App ID:', info.app_id); -console.log('Instance ID:', info.instance_id); -console.log('App Name:', info.app_name); -console.log('TCB Info:', info.tcb_info); - -// Derive deterministic keys for blockchain applications -const walletKey = await client.getKey('wallet/ethereum', 'mainnet'); -console.log('Derived key (32 bytes):', walletKey.key); // secp256k1 private key -console.log('Signature chain:', walletKey.signature_chain); // Authenticity proof - -// Generate remote attestation quote -const applicationData = JSON.stringify({ - version: '1.0.0', - timestamp: Date.now(), - user_id: 'alice' -}); - -const quote = await client.getQuote(applicationData); -console.log('TDX Quote:', quote.quote); -console.log('Event Log:', quote.event_log); - -// Verify measurement registers -const rtmrs = quote.replayRtmrs(); -console.log('RTMR0-3:', rtmrs); +// Generate an attestation quote +const quote = await client.getQuote('my-app-state'); +console.log(quote.quote); ``` -### Version Compatibility - -- **dstack OS 0.5.x**: Use `/var/run/dstack.sock` (current) -- **dstack OS 0.3.x**: Use `/var/run/tappd.sock` (deprecated but supported) - -The SDK automatically detects the correct socket path, but you must ensure the appropriate volume binding in your Docker Compose configuration. - -## Advanced Features - -### TLS Certificate Generation - -Generate fresh TLS certificates with optional Remote Attestation support. **Important**: `getTlsKey()` generates random keys on each call - it's designed specifically for TLS/SSL scenarios where fresh keys are required. +The client automatically connects to `/var/run/dstack.sock`. For local development with the simulator: ```typescript -// Generate TLS certificate with different usage scenarios -const tlsKey = await client.getTlsKey({ - subject: 'my-secure-service', // Certificate common name - altNames: ['localhost', '127.0.0.1'], // Additional valid domains/IPs - usageRaTls: true, // Include remote attestation - usageServerAuth: true, // Enable server authentication (default) - usageClientAuth: false // Disable client authentication -}); - -console.log('Private Key (PEM):', tlsKey.key); -console.log('Certificate Chain:', tlsKey.certificate_chain); - -// ⚠️ WARNING: Each call generates a different key -const tlsKey1 = await client.getTlsKey(); -const tlsKey2 = await client.getTlsKey(); -// tlsKey1.key !== tlsKey2.key (always different!) +const client = new DstackClient('http://localhost:8090'); ``` -### Event Logging - -> [!NOTE] -> This feature isn't available in the simulator. We recommend sticking with `report_data` for most cases since it's simpler and safer to use. If you're not super familiar with SGX/TDX attestation quotes, it's best to avoid adding data directly into quotes as it could cause verification issues. - -Extend RTMR3 with custom events for audit trails: - -```typescript -// Emit custom events (requires dstack OS 0.5.0+) -await client.emitEvent('user-action', JSON.stringify({ - action: 'transfer', - amount: 1000, - timestamp: Date.now() -})); - -// Events are automatically included in subsequent quotes -const quote = await client.getQuote('audit-data'); -const events = JSON.parse(quote.event_log); -``` +## Core API -## Blockchain Integration +### Derive Keys -### Ethereum with Viem +`getKey()` derives deterministic keys bound to your application's identity (`app_id`). The same path always produces the same key for your app, but different apps get different keys even with the same path. ```typescript -import { toViemAccount, toViemAccountSecure } from '@phala/dstack-sdk/viem'; -import { createWalletClient, http } from 'viem'; -import { mainnet } from 'viem/chains'; - -const keyResult = await client.getKey('ethereum/main', 'wallet'); - -// Standard account creation -const account = toViemAccount(keyResult); - -// Enhanced security with SHA256 hashing (recommended) -const secureAccount = toViemAccountSecure(keyResult); - -const wallet = createWalletClient({ - account: secureAccount, - chain: mainnet, - transport: http() -}); +// Derive keys by path +const ethKey = await client.getKey('wallet/ethereum'); +const btcKey = await client.getKey('wallet/bitcoin'); -// Use wallet for transactions -const hash = await wallet.sendTransaction({ - to: '0x...', - value: parseEther('0.1') -}); +// Use path to separate keys +const mainnetKey = await client.getKey('wallet/eth/mainnet'); +const testnetKey = await client.getKey('wallet/eth/testnet'); ``` -### Solana - -```typescript -import { toKeypair, toKeypairSecure } from '@phala/dstack-sdk/solana'; -import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js'; - -const keyResult = await client.getKey('solana/main', 'wallet'); +**Parameters:** +- `path`: Key derivation path (determines the key) +- `purpose` (optional): Included in signature chain message, does not affect the derived key -// Standard keypair creation -const keypair = toKeypair(keyResult); +**Returns:** `GetKeyResponse` +- `key`: Hex-encoded private key +- `signature_chain`: Signatures proving the key was derived in a genuine TEE -// Enhanced security with SHA256 hashing (recommended) -const secureKeypair = toKeypairSecure(keyResult); +### Generate Attestation Quotes -const connection = new Connection('https://api.mainnet-beta.solana.com'); +`getQuote()` creates a TDX quote proving your code runs in a genuine TEE. -// Create and send transaction -const transaction = new Transaction().add( - SystemProgram.transfer({ - fromPubkey: secureKeypair.publicKey, - toPubkey: new PublicKey('...'), - lamports: 1000000 - }) -); +```typescript +const quote = await client.getQuote('user:alice:nonce123'); -const signature = await connection.sendTransaction(transaction, [secureKeypair]); +// Replay RTMRs from the event log +const rtmrs = quote.replayRtmrs(); +console.log(rtmrs); ``` -## Environment Variables Encryption - -**Important**: This feature is specifically for **deployment-time security**, not runtime SDK operations. - -The SDK provides end-to-end encryption capabilities for securely transmitting sensitive environment variables during dstack application deployment. When deploying applications to TEE instances, sensitive configuration data (API keys, database credentials, private keys, etc.) needs to be securely transmitted from the deployment client to the TEE application. - -### Deployment Security Problem - -During application deployment, sensitive data must traverse: -1. **Client Environment** → Deployment infrastructure → **TEE Application** -2. **Risk**: Deployment infrastructure could potentially access plaintext secrets -3. **Solution**: Client-side encryption ensures only the target TEE application can decrypt secrets - -### How It Works - -1. **Pre-Deployment**: Client obtains encryption public key from KMS API -2. **Encryption**: Client encrypts environment variables using X25519 + AES-GCM -3. **Transmission**: Encrypted payload is sent through deployment infrastructure -4. **Decryption**: TEE application automatically decrypts and loads environment variables -5. **Runtime**: Application accesses secrets via standard `process.env` - -This ensures **true end-to-end encryption** where deployment infrastructure never sees plaintext secrets. +**Parameters:** +- `reportData`: Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256). -### App Configuration for Encrypted Variables +**Returns:** `GetQuoteResponse` +- `quote`: Hex-encoded TDX quote +- `event_log`: JSON string of measured events +- `replayRtmrs()`: Method to compute RTMR values from event log -Your `app-compose.json` should specify which environment variables are allowed: +### Get Instance Info -```json -{ - "manifest_version": 1, - "name": "secure-app", - "runner": "docker-compose", - "docker_compose_file": "services:\n app:\n build: .\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - API_KEY\n - DATABASE_URL\n - PRIVATE_KEY", - "allowed_envs": ["API_KEY", "DATABASE_URL", "PRIVATE_KEY"], - "kms_enabled": true -} +```typescript +const info = await client.info(); +console.log(info.app_id); +console.log(info.instance_id); +console.log(info.tcb_info); ``` -### Deployment Encryption Workflow +**Returns:** `InfoResponse` +- `app_id`: Application identifier +- `instance_id`: Instance identifier +- `app_name`: Application name +- `tcb_info`: TCB measurements (MRTD, RTMRs, event log) +- `compose_hash`: Hash of the app configuration +- `app_cert`: Application certificate (PEM) -```typescript -import { encryptEnvVars, verifyEnvEncryptPublicKey, type EnvVar } from '@phala/dstack-sdk'; +### Generate TLS Certificates -// 1. Define sensitive environment variables -const envVars: EnvVar[] = [ - { key: 'DATABASE_URL', value: 'postgresql://user:pass@host:5432/db' }, - { key: 'API_SECRET_KEY', value: 'your-secret-key' }, - { key: 'JWT_PRIVATE_KEY', value: '-----BEGIN PRIVATE KEY-----\n...' }, - { key: 'WALLET_MNEMONIC', value: 'abandon abandon abandon...' } -]; +`getTlsKey()` creates fresh TLS certificates. Unlike `getKey()`, each call generates a new random key. -// 2. Obtain encryption public key from KMS API (dstack-vmm or Phala Cloud) -const response = await fetch('/prpc/GetAppEnvEncryptPubKey?json', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ app_id: 'your-app-id-hex' }) +```typescript +const tls = await client.getTlsKey({ + subject: 'api.example.com', + altNames: ['localhost'], + usageRaTls: true // Embed attestation in certificate }); -const { public_key, signature } = await response.json(); - -// 3. Verify KMS API authenticity to prevent man-in-the-middle attacks -const publicKeyBytes = new Uint8Array(Buffer.from(public_key, 'hex')); -const signatureBytes = new Uint8Array(Buffer.from(signature, 'hex')); - -const trustedPubkey = verifyEnvEncryptPublicKey(publicKeyBytes, signatureBytes, 'your-app-id-hex'); -if (!trustedPubkey) { - throw new Error('KMS API provided untrusted encryption key'); -} - -console.log('Verified KMS public key:', trustedPubkey); -// 4. Encrypt environment variables for secure deployment -const encryptedData = await encryptEnvVars(envVars, public_key); -console.log('Encrypted payload:', encryptedData); - -// 5. Deploy with encrypted configuration -await deployDstackApp({ - app_id: 'your-app-id-hex', - encrypted_env: encryptedData, - // ... other deployment parameters -}); +console.log(tls.key); // PEM private key +console.log(tls.certificate_chain); // Certificate chain ``` -### Security Guarantees - -The environment encryption system provides several security guarantees: - -**End-to-End Encryption**: Environment variables are encrypted on the client side and can only be decrypted by the target dstack application inside the TEE. Even the deployment infrastructure cannot access the plaintext values. - -**KMS Authenticity Verification**: The `verifyEnvEncryptPublicKey` function validates that the encryption public key comes from a trusted KMS (Key Management Service), preventing man-in-the-middle attacks during key exchange. - -**Forward Secrecy**: Each encryption operation uses ephemeral X25519 keypairs, ensuring that compromising long-term keys cannot decrypt past communications. - -**Authenticated Encryption**: AES-256-GCM provides both confidentiality and integrity protection, detecting any tampering with encrypted data. - -### Encryption Protocol Details - -The encryption process follows this cryptographic protocol: - -1. **Key Exchange**: Generate ephemeral X25519 keypair and perform ECDH with KMS public key -2. **Shared Secret**: Derive AES-256 key from ECDH shared secret -3. **Authenticated Encryption**: Encrypt JSON payload using AES-256-GCM with random IV -4. **Payload Format**: `ephemeral_pubkey(32) + iv(12) + encrypted_data + auth_tag(16)` - -```typescript -// Detailed encryption example -const envVars = [{ key: 'SECRET', value: 'sensitive-data' }]; -const kmsPublicKey = '0xa1b2c3d4...'; // From trusted KMS API - -const encrypted = await encryptEnvVars(envVars, kmsPublicKey); -// encrypted = "a1b2c3..." (hex string) +**Parameters:** +- `subject` (optional): Certificate common name (e.g., domain name) +- `altNames` (optional): List of subject alternative names +- `usageRaTls` (optional): Embed TDX quote in certificate extension +- `usageServerAuth` (optional): Enable for server authentication (default: `true`) +- `usageClientAuth` (optional): Enable for client authentication (default: `false`) -// Inside dstack application, the encrypted data is automatically decrypted -// and made available as environment variables: -console.log(process.env.SECRET); // "sensitive-data" -``` +**Returns:** `GetTlsKeyResponse` +- `key`: PEM-encoded private key +- `certificate_chain`: List of PEM certificates -### KMS Public Key Verification +### Sign and Verify -The verification function ensures the encryption public key comes from a legitimate KMS: +Sign data using TEE-derived keys (not yet released): ```typescript -import { verifyEnvEncryptPublicKey } from '@phala/dstack-sdk'; - -/** - * Verify KMS-provided encryption public key authenticity - * - * @param publicKey - X25519 public key bytes (32 bytes) - * @param signature - secp256k1 signature from KMS (65 bytes) - * @param appId - Target application ID (hex string) - * @returns Compressed secp256k1 public key of KMS if valid, null if invalid - */ -const kmsIdentity = verifyEnvEncryptPublicKey(publicKeyBytes, signatureBytes, appId); +const result = await client.sign('ed25519', 'message to sign'); +console.log(result.signature); +console.log(result.public_key); -if (kmsIdentity) { - console.log('Trusted KMS identity:', kmsIdentity); - // Proceed with encryption using the verified public key -} else { - throw new Error('KMS signature verification failed - potential MITM attack'); -} +// Verify the signature +const valid = await client.verify('ed25519', 'message to sign', result.signature, result.public_key); +console.log(valid.valid); // true ``` -### Message Format for Verification +**`sign()` Parameters:** +- `algorithm`: `'ed25519'`, `'secp256k1'`, or `'secp256k1_prehashed'` +- `data`: Data to sign (string, Buffer, or Uint8Array) -The KMS signature covers the following message structure: -``` -message = "dstack-env-encrypt-pubkey" + ":" + app_id + public_key -signature = secp256k1.sign(keccak256(message), kms_private_key) -``` +**`sign()` Returns:** `SignResponse` +- `signature`: Hex-encoded signature +- `public_key`: Hex-encoded public key +- `signature_chain`: Signatures proving TEE origin -This binds the encryption key to a specific application ID and prevents key substitution attacks. +**`verify()` Parameters:** +- `algorithm`: Algorithm used for signing +- `data`: Original data +- `signature`: Signature to verify +- `public_key`: Public key to verify against -## Cryptographic Security +**`verify()` Returns:** `VerifyResponse` +- `valid`: Boolean indicating if signature is valid -### Key Derivation Security +### Emit Events -The SDK implements secure key derivation using: - -- **Deterministic Generation**: Keys are derived using HMAC-based Key Derivation Function (HKDF) -- **Application Isolation**: Each path produces unique keys, preventing cross-application access -- **Signature Verification**: All derived keys include cryptographic proof of origin -- **TEE Protection**: Master keys never leave the secure enclave +Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record. ```typescript -// Each path generates a unique, deterministic key -const wallet1 = await client.getKey('app1/wallet', 'ethereum'); -const wallet2 = await client.getKey('app2/wallet', 'ethereum'); -// wallet1.key !== wallet2.key (guaranteed different) - -const sameWallet = await client.getKey('app1/wallet', 'ethereum'); -// wallet1.key === sameWallet.key (guaranteed identical) +await client.emitEvent('config_loaded', 'production'); +await client.emitEvent('plugin_initialized', 'auth-v2'); ``` -### Remote Attestation +**Parameters:** +- `event`: Event name (string identifier) +- `payload`: Event value (string, Buffer, or Uint8Array) -TDX quotes provide cryptographic proof of: +## Blockchain Integration -- **Code Integrity**: Measurement of loaded application code -- **Data Integrity**: Inclusion of application-specific data in quote -- **Environment Authenticity**: Verification of TEE platform and configuration +### Ethereum with Viem ```typescript -const applicationState = JSON.stringify({ - version: '1.0.0', - config_hash: 'sha256:...', - timestamp: Date.now() -}); +import { toViemAccount } from '@phala/dstack-sdk/viem'; +import { createWalletClient, http } from 'viem'; +import { mainnet } from 'viem/chains'; -const quote = await client.getQuote(applicationState); +const key = await client.getKey('wallet/ethereum'); +const account = toViemAccount(key); -// Quote can be verified by external parties to confirm: -// 1. Application is running in genuine TEE -// 2. Application code matches expected measurements -// 3. Application state is authentic and unmodified +const wallet = createWalletClient({ + account, + chain: mainnet, + transport: http() +}); ``` -### Environment Encryption Protocol - -The encryption scheme uses: +### Solana -- **X25519 ECDH**: Elliptic curve key exchange for forward secrecy -- **AES-256-GCM**: Authenticated encryption with 256-bit keys -- **Ephemeral Keys**: New keypair generated for each encryption operation -- **Authenticated Data**: Prevents tampering and ensures integrity +```typescript +import { toKeypair } from '@phala/dstack-sdk/solana'; -## Development and Testing +const key = await client.getKey('wallet/solana'); +const keypair = toKeypair(key); +console.log(keypair.publicKey.toBase58()); +``` -### Local Development +## Development -For development without physical TDX hardware: +For local development without TDX hardware, use the simulator: ```bash -# Clone and build simulator git clone https://github.com/Dstack-TEE/dstack.git cd dstack/sdk/simulator ./build.sh ./dstack-simulator - -# Set environment variable -export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 ``` -### Testing Connectivity - -```typescript -const client = new DstackClient(); - -// Check if dstack service is available -const isAvailable = await client.isReachable(); -if (!isAvailable) { - console.error('dstack service is not reachable'); - process.exit(1); -} -``` +Then set the endpoint: -## API Reference - -### DstackClient - -#### Constructor - -```typescript -new DstackClient(endpoint?: string) -``` - -**Parameters:** -- `endpoint` (optional): Connection endpoint - - Unix socket path (production): `/var/run/dstack.sock` - - HTTP/HTTPS URL (development): `http://localhost:8090` - - Environment variable: `DSTACK_SIMULATOR_ENDPOINT` - -**Production App Configuration:** - -The Docker Compose configuration is embedded in `app-compose.json`: - -```json -{ - "manifest_version": 1, - "name": "production-app", - "runner": "docker-compose", - "docker_compose_file": "services:\n app:\n image: your-app\n volumes:\n - /var/run/dstack.sock:/var/run/dstack.sock\n environment:\n - NODE_ENV=production", - "public_tcbinfo": true -} -``` - -**Important**: The `docker_compose_file` contains YAML content as a string, ensuring the volume binding for `/var/run/dstack.sock` is included. - -#### Methods - -##### `info(): Promise` - -Retrieves comprehensive information about the TEE instance. - -**Returns:** `InfoResponse` -- `app_id`: Unique application identifier -- `instance_id`: Unique instance identifier -- `app_name`: Application name from configuration -- `device_id`: TEE device identifier -- `tcb_info`: Trusted Computing Base information - - `mrtd`: Measurement of TEE domain - - `rtmr0-3`: Runtime Measurement Registers - - `event_log`: Boot and runtime events - - `os_image_hash`: Operating system measurement - - `compose_hash`: Application configuration hash -- `app_cert`: Application certificate in PEM format -- `key_provider_info`: Key management configuration - -##### `getKey(path: string, purpose?: string): Promise` - -Derives a deterministic secp256k1/K256 private key for blockchain and Web3 applications. This is the primary method for obtaining cryptographic keys for wallets, signing, and other deterministic key scenarios. - -**Parameters:** -- `path`: Unique identifier for key derivation (e.g., `"wallet/ethereum"`, `"signing/solana"`) -- `purpose` (optional): Additional context for key usage (default: `""`) - -**Returns:** `GetKeyResponse` -- `key`: 32-byte secp256k1 private key as `Uint8Array` (suitable for Ethereum, Bitcoin, Solana, etc.) -- `signature_chain`: Array of cryptographic signatures proving key authenticity - -**Key Characteristics:** -- **Deterministic**: Same path + purpose always generates identical key -- **Isolated**: Different paths produce cryptographically independent keys -- **Blockchain-Ready**: Compatible with secp256k1 curve (Ethereum, Bitcoin, Solana) -- **Verifiable**: Signature chain proves key was derived inside genuine TEE - -**Use Cases:** -- Cryptocurrency wallets -- Transaction signing -- DeFi protocol interactions -- NFT operations -- Any scenario requiring consistent, reproducible keys - -```typescript -// Examples of deterministic key derivation -const ethWallet = await client.getKey('wallet/ethereum', 'mainnet'); -const btcWallet = await client.getKey('wallet/bitcoin', 'mainnet'); -const solWallet = await client.getKey('wallet/solana', 'mainnet'); - -// Same path always returns same key -const key1 = await client.getKey('my-app/signing'); -const key2 = await client.getKey('my-app/signing'); -// key1.key === key2.key (guaranteed identical) - -// Different paths return different keys -const userA = await client.getKey('user/alice/wallet'); -const userB = await client.getKey('user/bob/wallet'); -// userA.key !== userB.key (guaranteed different) +```bash +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 ``` -##### `getQuote(reportData: string | Buffer | Uint8Array): Promise` - -Generates a TDX attestation quote containing the provided report data. - -**Parameters:** -- `reportData`: Data to include in quote (max 64 bytes) - -**Returns:** `GetQuoteResponse` -- `quote`: TDX quote as hex string -- `event_log`: JSON string of system events -- `replayRtmrs()`: Function returning computed RTMR values - -**Use Cases:** -- Remote attestation of application state -- Cryptographic proof of execution environment -- Audit trail generation - -##### `getTlsKey(options?: TlsKeyOptions): Promise` - -Generates a fresh, random TLS key pair with X.509 certificate for TLS/SSL connections. **Important**: This method generates different keys on each call - use `getKey()` for deterministic keys. - -**Parameters:** `TlsKeyOptions` -- `path` (optional): Path parameter (unused in current implementation) -- `subject` (optional): Certificate subject (Common Name) - typically the domain name (default: `""`) -- `altNames` (optional): Subject Alternative Names - additional domains/IPs for the certificate (default: `[]`) -- `usageRaTls` (optional): Include TDX attestation quote in certificate extension for remote verification (default: `false`) -- `usageServerAuth` (optional): Enable server authentication - allows certificate to authenticate servers (default: `true`) -- `usageClientAuth` (optional): Enable client authentication - allows certificate to authenticate clients (default: `false`) - -**Returns:** `GetTlsKeyResponse` -- `key`: Private key in PEM format (X.509/PKCS#8) -- `certificate_chain`: Certificate chain array - -**Key Characteristics:** -- **Random Generation**: Each call produces a completely different key -- **TLS-Optimized**: Keys and certificates designed for TLS/SSL scenarios -- **RA-TLS Support**: Optional remote attestation extension in certificates -- **TEE-Signed**: Certificates signed by TEE-resident Certificate Authority +--- -**Certificate Usage Scenarios:** +## Deployment Utilities -1. **Standard HTTPS Server** (`usageServerAuth: true`, `usageClientAuth: false`) - - Web servers, API endpoints - - Server authenticates to clients - - Most common TLS use case +These utilities are for deployment scripts, not runtime SDK operations. -2. **Remote Attestation Server** (`usageRaTls: true`) - - TEE-based services requiring proof of execution environment - - Clients can verify the server runs in genuine TEE - - Combines TLS with hardware attestation +### Encrypt Environment Variables -3. **mTLS Client Certificate** (`usageServerAuth: false`, `usageClientAuth: true`) - - Client authentication in mutual TLS - - API clients, service-to-service communication - - Client proves identity to server - -4. **Dual-Purpose Certificate** (`usageServerAuth: true`, `usageClientAuth: true`) - - Services that act as both client and server - - Microservices architectures - - Maximum flexibility for TLS roles +Encrypt secrets before deploying to dstack: ```typescript -// Example 1: Standard HTTPS server certificate -const serverCert = await client.getTlsKey({ - subject: 'api.example.com', - altNames: ['api.example.com', 'www.api.example.com', '10.0.0.1'] - // usageServerAuth: true (default) - allows server authentication - // usageClientAuth: false (default) - no client authentication -}); - -// Example 2: Certificate with remote attestation (RA-TLS) -const attestedCert = await client.getTlsKey({ - subject: 'secure-api.example.com', - usageRaTls: true // Include TDX quote for remote verification - // Clients can verify the TEE environment through the certificate -}); - -// Example 3: Mutual TLS (mTLS) certificate for client authentication -const clientCert = await client.getTlsKey({ - subject: 'client.example.com', - usageServerAuth: false, // This certificate won't authenticate servers - usageClientAuth: true // Enable client authentication -}); +import { encryptEnvVars, verifyEnvEncryptPublicKey, type EnvVar } from '@phala/dstack-sdk'; -// Example 4: Certificate for both server and client authentication -const dualUseCert = await client.getTlsKey({ - subject: 'dual.example.com', - usageServerAuth: true, // Can authenticate as server - usageClientAuth: true // Can authenticate as client -}); +// Get and verify the KMS public key +// (obtain public_key and signature from KMS API) +const kmsIdentity = verifyEnvEncryptPublicKey(publicKeyBytes, signatureBytes, appId); +if (!kmsIdentity) { + throw new Error('Invalid KMS key'); +} -// ⚠️ Each call generates different keys (unlike getKey) -const cert1 = await client.getTlsKey(); -const cert2 = await client.getTlsKey(); -// cert1.key !== cert2.key (always different) - -// Use with Node.js HTTPS server -import https from 'https'; -const server = https.createServer({ - key: serverCert.key, - cert: serverCert.certificate_chain.join('\n') -}, app); +// Encrypt variables +const envVars: EnvVar[] = [ + { key: 'DATABASE_URL', value: 'postgresql://...' }, + { key: 'API_KEY', value: 'secret' } +]; +const encrypted = await encryptEnvVars(envVars, publicKey); ``` -##### `emitEvent(event: string, payload: string | Buffer | Uint8Array): Promise` - -Extends RTMR3 with a custom event for audit logging. - -**Parameters:** -- `event`: Event identifier string -- `payload`: Event data - -**Requirements:** -- dstack OS version 0.5.0 or later -- Events are permanently recorded in TEE measurements - -##### `isReachable(): Promise` - -Tests connectivity to the dstack service. - -**Returns:** `boolean` indicating service availability - -## Utility Functions - -### Compose Hash Calculation +### Calculate Compose Hash ```typescript import { getComposeHash } from '@phala/dstack-sdk'; -const appCompose = { - manifest_version: 1, - name: 'my-app', - runner: 'docker-compose', - docker_compose_file: 'docker-compose.yml' -}; - -const hash = getComposeHash(appCompose); -console.log('Configuration hash:', hash); -``` - -### KMS Public Key Verification - -Verify the authenticity of encryption public keys provided by KMS APIs: - -```typescript -import { verifyEnvEncryptPublicKey } from '@phala/dstack-sdk'; - -// Example: Verify KMS-provided encryption key -const publicKey = Buffer.from('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a', 'hex'); -const signature = Buffer.from('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00', 'hex'); -const appId = '0000000000000000000000000000000000000000'; - -const kmsIdentity = verifyEnvEncryptPublicKey( - new Uint8Array(publicKey), - new Uint8Array(signature), - appId -); - -if (kmsIdentity) { - console.log('Trusted KMS identity:', kmsIdentity); - // Safe to use the public key for encryption -} else { - console.error('KMS signature verification failed'); - // Potential man-in-the-middle attack -} +const hash = getComposeHash(appComposeObject); ``` -## Security Best Practices - -1. **Key Management** - - Use descriptive, unique paths for key derivation - - Never expose derived keys outside the TEE - - Implement proper access controls in your application - -2. **Remote Attestation** - - Always verify quotes before trusting remote TEE instances - - Include application-specific data in quote generation - - Validate RTMR measurements against expected values - -3. **TLS Configuration** - - Enable RA-TLS for attestation-based authentication - - Use appropriate certificate validity periods - - Implement proper certificate validation - -4. **Error Handling** - - Handle cryptographic operation failures gracefully - - Log security events for monitoring - - Implement fallback mechanisms where appropriate - -## Migration Guide - -### Critical API Changes: Understanding the Separation +--- -The legacy `deriveKey()` method mixed two different use cases that have now been properly separated: +## Migration from TappdClient -1. **`getKey()`**: Deterministic key derivation for Web3/blockchain (secp256k1) -2. **`getTlsKey()`**: Random TLS certificate generation for HTTPS/SSL - -### Method Comparison Table - -| Feature | `getKey()` | `getTlsKey()` | -|---------|------------|---------------| -| **Purpose** | Web3/Blockchain keys | TLS/SSL certificates | -| **Key Generation** | Deterministic (same input = same key) | Random (different every call) | -| **Key Format** | Raw 32-byte secp256k1 private key | PEM-formatted X.509 private key | -| **Use Cases** | Wallets, signing, DeFi, NFT | HTTPS servers, mTLS, secure APIs | -| **Algorithm/Curve** | ECDSA/secp256k1 (k256) | ECDSA/NIST P-256 | -| **Returns** | `{ key: Uint8Array, signature_chain }` | `{ key: string (PEM), certificate_chain }` | -| **Reproducible** | ✅ Yes (same path = same key) | ❌ No (random each time) | -| **Blockchain Ready** | ✅ Yes (Ethereum, Bitcoin, Solana) | ❌ No (TLS-specific format) | -| **Certificate** | ❌ No certificate | ✅ Yes (X.509 certificate chain) | -| **RA-TLS Support** | ❌ No | ✅ Yes (optional) | - -### When to Use Which Method - -**Use `getKey()` when you need:** -- Cryptocurrency wallets -- Transaction signing -- Consistent keys across app restarts -- Web3 integrations (Ethereum, Solana, etc.) -- Any deterministic cryptographic operations - -**Use `getTlsKey()` when you need:** -- HTTPS server certificates -- Client authentication certificates -- Fresh keys for each connection -- Standard TLS/SSL setups -- Remote attestation via RA-TLS - -### From TappdClient to DstackClient - -**⚠️ BREAKING CHANGE**: `TappdClient` is deprecated and will be removed. All users must migrate to `DstackClient`. - -### Complete Migration Reference - -| Component | TappdClient (Old) | DstackClient (New) | Status | -|-----------|-------------------|-------------------|---------| -| **Socket Path** | `/var/run/tappd.sock` | `/var/run/dstack.sock` | ✅ Updated | -| **HTTP URL Format** | `http://localhost/prpc/Tappd.` | `http://localhost/` | ✅ Simplified | -| **K256 Key Method** | `DeriveK256Key(...)` | `GetKey(...)` | ✅ Renamed | -| **TLS Certificate Method** | `DeriveKey(...)` | `GetTlsKey(...)` | ✅ Separated | -| **TDX Quote (Hash)** | `TdxQuote(...)` | ❌ **Removed** | ⚠️ No longer supported | -| **TDX Quote (Raw)** | `RawQuote(...)` | `GetQuote(report_data)` | ✅ Renamed | - -#### For Web3/Blockchain Applications (Most Common Use Case) - -```typescript -// ❌ OLD - TappdClient.deriveKey() (DEPRECATED) -import { TappdClient } from '@phala/dstack-sdk'; -const client = new TappdClient(); -const result = await client.deriveKey('wallet/ethereum', 'mainnet'); -// This actually returned deterministic keys (confusing!) - -// ✅ NEW - DstackClient.getKey() (RECOMMENDED) -import { DstackClient } from '@phala/dstack-sdk'; -const client = new DstackClient(); -const result = await client.getKey('wallet/ethereum', 'mainnet'); -// Clear intent: deterministic key for blockchain usage -``` - -#### For TLS/SSL Certificate Generation - -```typescript -// ❌ OLD - TappdClient.deriveKey() with TLS options (CONFUSING) -import { TappdClient } from '@phala/dstack-sdk'; -const client = new TappdClient(); -const result = await client.deriveKey('server', 'api.example.com', ['localhost']); -// Mixed deterministic keys with TLS concepts - -// ✅ NEW - DstackClient.getTlsKey() (CLEAR PURPOSE) -import { DstackClient } from '@phala/dstack-sdk'; -const client = new DstackClient(); -const result = await client.getTlsKey({ - subject: 'api.example.com', - altNames: ['localhost'] -}); -// Clear intent: random TLS certificate generation -``` - -### Migration Steps - -#### Step 1: Update Imports and Client +Replace `TappdClient` with `DstackClient`: ```typescript // Before import { TappdClient } from '@phala/dstack-sdk'; const client = new TappdClient(); -// After -import { DstackClient } from '@phala/dstack-sdk'; -const client = new DstackClient(); -``` - -#### Step 2: Choose the Right Method - -**For Web3/Blockchain (95% of use cases):** -```typescript -// Before: deriveKey() for wallet/signing -const walletKey = await client.deriveKey('wallet', 'ethereum'); - -// After: getKey() for deterministic keys -const walletKey = await client.getKey('wallet', 'ethereum'); -``` - -**For TLS/SSL Certificates:** -```typescript -// Before: deriveKey() with subject/altNames -const tlsCert = await client.deriveKey('api', 'example.com', ['localhost']); - -// After: getTlsKey() with proper options -const tlsCert = await client.getTlsKey({ - subject: 'example.com', - altNames: ['localhost'] -}); -``` - -#### Step 3: Update Blockchain Integrations - -```typescript -// Viem (Ethereum) -import { toViemAccountSecure } from '@phala/dstack-sdk/viem'; - -// Before -const keyResult = await tappdClient.deriveKey('wallet'); -const account = toViemAccount(keyResult); // Basic security - -// After -const keyResult = await dstackClient.getKey('wallet', 'ethereum'); -const account = toViemAccountSecure(keyResult); // Enhanced security - -// Solana -import { toKeypairSecure } from '@phala/dstack-sdk/solana'; - -// Before -const keyResult = await tappdClient.deriveKey('wallet'); -const keypair = toKeypair(keyResult); // Basic security - // After -const keyResult = await dstackClient.getKey('wallet', 'solana'); -const keypair = toKeypairSecure(keyResult); // Enhanced security -``` - -### Why This Migration is Important - -1. **API Clarity**: Separate methods for separate purposes eliminates confusion -2. **Critical Security Fixes**: Legacy integration functions have security vulnerabilities -3. **Feature Deprecation**: Hash-based TDX quotes are no longer supported -4. **Future Compatibility**: `TappdClient` will be removed in future versions -5. **Better Type Safety**: Clearer interfaces and return types - -### Critical Security Issues - -**⚠️ SECURITY VULNERABILITY**: Legacy blockchain integration functions have security flaws: - -- **`toViemAccount()`**: Uses raw key material without proper hashing - **VULNERABLE** -- **`toKeypair()`**: Uses raw key material without proper hashing - **VULNERABLE** - -**✅ SECURE ALTERNATIVES**: Always use the secure versions: -- **`toViemAccountSecure()`**: Applies SHA256 hashing for enhanced security -- **`toKeypairSecure()`**: Applies SHA256 hashing for enhanced security - -### Compatibility Notes - -- **`TappdClient`** remains functional for now but shows deprecation warnings -- **`DstackClient.deriveKey()`** throws an error - forcing migration to correct methods -- **Legacy integration functions** work but have security vulnerabilities - **MUST MIGRATE** - -### Infrastructure Changes - -**Docker Volume Binding:** -```yaml -# Old (dstack OS 0.3.x) -volumes: - - /var/run/tappd.sock:/var/run/tappd.sock - -# New (dstack OS 0.5.x) -volumes: - - /var/run/dstack.sock:/var/run/dstack.sock -``` - -**Environment Variables:** -```bash -# Old -TAPPD_SIMULATOR_ENDPOINT=http://localhost:8090 - -# New -DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 -``` - -### API Method Changes - -**⚠️ MAJOR BREAKING CHANGE - TDX Quote Methods:** - -```typescript -// ❌ OLD - TappdClient with hash algorithms (REMOVED) -const client = new TappdClient(); -await client.tdxQuote('user-data', 'sha256'); // Hash applied automatically -await client.tdxQuote('user-data', 'keccak256'); // Multiple algorithms supported -await client.tdxQuote('user-data', 'raw'); // Raw mode - -// ✅ NEW - DstackClient raw data only (BREAKING CHANGE) +import { DstackClient } from '@phala/dstack-sdk'; const client = new DstackClient(); -await client.getQuote('user-data'); // Raw data only, max 64 bytes -``` - -**Critical Changes:** -1. **No Hash Algorithms**: `getQuote()` only accepts raw data, no automatic hashing -2. **Size Limit Change**: Report data must be ≤ 64 bytes (was flexible with hashing) -3. **Manual Hashing**: If you need hashing, do it manually before calling `getQuote()` - -**Migration Example:** -```typescript -// Old approach with automatic hashing -const result = await tappdClient.tdxQuote('my-application-data', 'sha256'); - -// New approach - manual hashing if needed -import { createHash } from 'crypto'; -const hash = createHash('sha256').update('my-application-data').digest(); -const result = await dstackClient.getQuote(hash); // hash is 32 bytes -``` - -**Key Derivation Methods:** -```typescript -// Old internal method names (handled automatically by SDK) -// DeriveK256Key -> GetKey -// DeriveKey -> GetTlsKey -// RawQuote -> GetQuote ``` -### Migration Checklist - -- [ ] **Infrastructure Updates:** - - [ ] Update Docker volume binding to `/var/run/dstack.sock` - - [ ] Change environment variables from `TAPPD_*` to `DSTACK_*` - -- [ ] **Client Code Updates:** - - [ ] Replace `TappdClient` with `DstackClient` - - [ ] Replace `deriveKey()` calls with appropriate method: - - [ ] `getKey()` for Web3/blockchain keys (deterministic) - - [ ] `getTlsKey()` for TLS certificates (random) - - [ ] **CRITICAL**: Migrate TDX quote methods: - - [ ] Replace all `tdxQuote()` calls with `getQuote()` - - [ ] Remove hash algorithm parameters (`'sha256'`, `'keccak256'`, etc.) - - [ ] Add manual hashing if needed (data must be ≤ 64 bytes) - - [ ] Test with smaller data or pre-hash large data - - [ ] **SECURITY CRITICAL**: Update blockchain integration functions: - - [ ] Replace `toViemAccount()` with `toViemAccountSecure()` (Ethereum) - - [ ] Replace `toKeypair()` with `toKeypairSecure()` (Solana) - - [ ] **DO NOT** use legacy functions - they have security vulnerabilities - -- [ ] **Testing:** - - [ ] Test that deterministic keys still work as expected - - [ ] Verify TLS certificate generation works - - [ ] **CRITICAL**: Test quote generation changes: - - [ ] Verify raw data fits in 64 bytes - - [ ] Test manual hashing if using large data - - [ ] Confirm quotes work without algorithm parameters - - [ ] **SECURITY**: Validate blockchain integrations: - - [ ] Test Ethereum with `toViemAccountSecure()` only - - [ ] Test Solana with `toKeypairSecure()` only - - [ ] Verify legacy functions are completely removed from codebase +Method changes: +- `deriveKey()` → `getTlsKey()` for TLS certificates +- `tdxQuote()` → `getQuote()` (raw data only, no hash algorithms) +- Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` ## License diff --git a/sdk/js/package.json b/sdk/js/package.json index 9cb6d3d8..01946b89 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -87,7 +87,11 @@ "test:ci": "vitest --run", "release": "npm run clean && npm run build && npm publish --access public" }, - "keywords": ["sdk", "dstack", "Phala"], + "keywords": [ + "sdk", + "dstack", + "Phala" + ], "author": "Leechael Yim", "license": "Apache-2.0", "dependencies": { @@ -99,15 +103,13 @@ "vitest": "^3.2.4" }, "optionalDependencies": { - "viem": "^2.21.0 <3.0.0", "@noble/curves": "^1.8.1", - "@solana/web3.js": "^1.98.0" + "@solana/web3.js": "^1.98.4", + "viem": "^2.43.3" }, "peerDependencies": { - "viem": "^2.21.0 <3.0.0", "@noble/curves": "^1.8.1", - "@noble/hashes": "^1.6.1", - "@solana/web3.js": "^1.98.0" + "@noble/hashes": "^1.6.1" }, "peerDependenciesMeta": { "viem": { diff --git a/sdk/python/README.md b/sdk/python/README.md index 8b6d521a..78a3c4f1 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -1,6 +1,6 @@ -# dstack SDK +# dstack SDK for Python -The dstack SDK provides a Python client for secure communication with the dstack Trusted Execution Environment (TEE). This SDK enables applications to derive cryptographic keys, generate remote attestation quotes, and perform other security-critical operations within confidential computing environments. +Access TEE features from your Python application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security. ## Installation @@ -8,833 +8,288 @@ The dstack SDK provides a Python client for secure communication with the dstack pip install dstack-sdk ``` -## Overview - -The dstack SDK enables secure communication with dstack Trusted Execution Environment (TEE) instances. dstack applications are defined using `app-compose.json` (based on the `AppCompose` structure) and deployed as containerized applications using Docker Compose. - -### Client Types - -The Python SDK provides both synchronous and asynchronous clients to accommodate different programming patterns: - -#### Synchronous Clients -- **`DstackClient`**: Main synchronous client for dstack services -- **`TappdClient`**: Deprecated synchronous client (use `DstackClient` instead) - -#### Asynchronous Clients -- **`AsyncDstackClient`**: Async/await compatible client for dstack services -- **`AsyncTappdClient`**: Deprecated async client (use `AsyncDstackClient` instead) - -**Key Differences:** -- **Synchronous clients** use regular method calls and block until completion -- **Asynchronous clients** use `async`/`await` syntax and allow concurrent operations -- **API compatibility**: Both client types provide identical methods and functionality -- **Performance**: Async clients excel in I/O-bound applications with multiple concurrent requests - -### Application Architecture - -dstack applications consist of: -- **App Configuration**: `app-compose.json` defining app metadata, security settings, and Docker Compose content -- **Container Deployment**: Docker Compose configuration embedded within the app definition -- **TEE Integration**: Access to TEE functionality via Unix socket (`/var/run/dstack.sock`) - -### SDK Capabilities - -- **Key Derivation**: Deterministic secp256k1 key generation for blockchain and Web3 applications -- **Remote Attestation**: TDX quote generation providing cryptographic proof of execution environment -- **TLS Certificate Management**: Fresh certificate generation with optional RA-TLS support for secure connections -- **Deployment Security**: Client-side encryption of sensitive environment variables ensuring secrets are only accessible to target TEE applications -- **Blockchain Integration**: Ready-to-use adapters for Ethereum and Solana ecosystems - -### Socket Connection Requirements - -To use the SDK, your Docker Compose configuration must bind-mount the dstack socket: - -```yaml -# docker-compose.yml -services: - your-app: - image: your-app-image - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock # dstack OS 0.5.x - # For dstack OS 0.3.x compatibility (deprecated): - # - /var/run/tappd.sock:/var/run/tappd.sock -``` - -## Basic Usage - -### Application Setup - -First, ensure your dstack application is properly configured: - -**1. App Configuration (`app-compose.json`)** -```json -{ - "manifest_version": 1, - "name": "my-secure-app", - "runner": "docker-compose", - "docker_compose_file": "services:\\n app:\\n build: .\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - NODE_ENV=production", - "public_tcbinfo": true, - "kms_enabled": false, - "gateway_enabled": false -} -``` - -**Note**: The `docker_compose_file` field contains the actual Docker Compose YAML content as a string, not a file path. - -### Synchronous Client Usage +## Quick Start ```python -import json -import time from dstack_sdk import DstackClient -# Create synchronous client - automatically connects to /var/run/dstack.sock client = DstackClient() -# For local development with simulator -dev_client = DstackClient('http://localhost:8090') +# Derive a deterministic key for your wallet +key = client.get_key('wallet/eth') +print(key.key) # Same path always returns the same key -# Get TEE instance information -info = client.info() -print('App ID:', info.app_id) -print('Instance ID:', info.instance_id) -print('App Name:', info.app_name) -print('TCB Info:', info.tcb_info) - -# Derive deterministic keys for blockchain applications -wallet_key = client.get_key('wallet/ethereum', 'mainnet') -print('Derived key (32 bytes):', wallet_key.decode_key()) # secp256k1 private key bytes -print('Signature chain:', wallet_key.signature_chain) # Authenticity proof - -# Generate remote attestation quote -application_data = json.dumps({ - "version": "1.0.0", - "timestamp": time.time(), - "user_id": "alice" -}) - -quote = client.get_quote(application_data.encode()) -print('TDX Quote:', quote.quote) -print('Event Log:', quote.event_log) - -# Verify measurement registers -rtmrs = quote.replay_rtmrs() -print('RTMR0-3:', rtmrs) +# Generate an attestation quote +quote = client.get_quote(b'my-app-state') +print(quote.quote) ``` -### Asynchronous Client Usage +The client automatically connects to `/var/run/dstack.sock`. For local development with the simulator, pass the endpoint explicitly: ```python -import json -import time -import asyncio -from dstack_sdk import AsyncDstackClient - -async def main(): - # Create asynchronous client - async_client = AsyncDstackClient() - - # All methods must be awaited - info = await async_client.info() - print('App ID:', info.app_id) - - # Derive keys asynchronously - wallet_key = await async_client.get_key('wallet/ethereum', 'mainnet') - print('Derived key:', wallet_key.decode_key().hex()) - - # Generate quote asynchronously - quote = await async_client.get_quote(b'async-test-data') - print('Quote length:', len(quote.quote)) - - # Multiple concurrent operations - tasks = [ - async_client.get_key('wallet/btc', 'mainnet'), - async_client.get_key('wallet/eth', 'mainnet'), - async_client.get_key('signing/key', 'production') - ] - - # Execute concurrently - keys = await asyncio.gather(*tasks) - for i, key in enumerate(keys): - print(f'Key {i}: {key.decode_key().hex()[:16]}...') - -# Run async code -if __name__ == "__main__": - asyncio.run(main()) +client = DstackClient('http://localhost:8090') ``` -### Choosing Between Sync and Async +## Core API -**Use Synchronous Client (`DstackClient`) when:** -- Building simple scripts or CLI tools -- Making sequential API calls -- Working in synchronous codebases -- Learning or prototyping +### Derive Keys -**Use Asynchronous Client (`AsyncDstackClient`) when:** -- Building web applications (FastAPI, Starlette, etc.) -- Needing concurrent TEE operations -- Integrating with async frameworks -- Optimizing I/O-bound applications +`get_key()` derives deterministic keys bound to your application's identity (`app_id`). The same path always produces the same key for your app, but different apps get different keys even with the same path. ```python -# Example: Concurrent key derivation (async advantage) -async def derive_multiple_keys(): - client = AsyncDstackClient() - - # This is much faster than sequential sync calls - keys = await asyncio.gather( - client.get_key('user/alice', 'eth'), - client.get_key('user/bob', 'eth'), - client.get_key('user/charlie', 'eth') - ) - return keys -``` +# Derive keys by path +eth_key = client.get_key('wallet/ethereum') +btc_key = client.get_key('wallet/bitcoin') -### Version Compatibility +# Use path to separate keys +mainnet_key = client.get_key('wallet/eth/mainnet') +testnet_key = client.get_key('wallet/eth/testnet') -- **dstack OS 0.5.x**: Use `/var/run/dstack.sock` (current) -- **dstack OS 0.3.x**: Use `/var/run/tappd.sock` (deprecated but supported) +# Different algorithm +ed_key = client.get_key('signing/key', algorithm='ed25519') +``` -The SDK automatically detects the correct socket path, but you must ensure the appropriate volume binding in your Docker Compose configuration. +**Parameters:** +- `path`: Key derivation path (determines the key) +- `purpose` (optional): Included in signature chain message, does not affect the derived key +- `algorithm` (optional): `'secp256k1'` (default) or `'ed25519'` -## Advanced Features +**Returns:** `GetKeyResponse` +- `key`: Hex-encoded private key +- `signature_chain`: Signatures proving the key was derived in a genuine TEE -### TLS Certificate Generation +### Generate Attestation Quotes -Generate fresh TLS certificates with optional Remote Attestation support. **Important**: `get_tls_key()` generates random keys on each call - it's designed specifically for TLS/SSL scenarios where fresh keys are required. +`get_quote()` creates a TDX quote proving your code runs in a genuine TEE. ```python -# Generate TLS certificate with different usage scenarios -tls_key = client.get_tls_key( - subject='my-secure-service', # Certificate common name - alt_names=['localhost', '127.0.0.1'], # Additional valid domains/IPs - usage_ra_tls=True, # Include remote attestation - usage_server_auth=True, # Enable server authentication (default) - usage_client_auth=False # Disable client authentication -) - -print('Private Key (PEM):', tls_key.key) -print('Certificate Chain:', tls_key.certificate_chain) +quote = client.get_quote(b'user:alice:nonce123') -# ⚠️ WARNING: Each call generates a different key -tls_key1 = client.get_tls_key() -tls_key2 = client.get_tls_key() -# tls_key1.key != tls_key2.key (always different!) +# Replay RTMRs from the event log +rtmrs = quote.replay_rtmrs() +print(rtmrs) ``` -### Event Logging +**Parameters:** +- `report_data`: Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256). -> [!NOTE] -> This feature isn't available in the simulator. We recommend sticking with `report_data` for most cases since it's simpler and safer to use. If you're not super familiar with SGX/TDX attestation quotes, it's best to avoid adding data directly into quotes as it could cause verification issues. +**Returns:** `GetQuoteResponse` +- `quote`: Hex-encoded TDX quote +- `event_log`: JSON string of measured events +- `replay_rtmrs()`: Method to compute RTMR values from event log -Extend RTMR3 with custom events for audit trails: +### Get Instance Info ```python -# Emit custom events (requires dstack OS 0.5.0+) -client.emit_event('user-action', json.dumps({ - "action": "transfer", - "amount": 1000, - "timestamp": time.time() -})) - -# Events are automatically included in subsequent quotes -quote = client.get_quote(b'audit-data') -events = json.loads(quote.event_log) +info = client.info() +print(info.app_id) +print(info.instance_id) +print(info.tcb_info) ``` -## Blockchain Integration - -### Ethereum - -```python -from dstack_sdk.ethereum import to_account - -key_result = client.get_key('ethereum/main', 'wallet') - -# Convert to Ethereum account -account = to_account(key_result) -print(f"Ethereum address: {account.address}") - -# Use with Web3.py -from web3 import Web3 -w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR-PROJECT-ID')) +**Returns:** `InfoResponse` +- `app_id`: Application identifier +- `instance_id`: Instance identifier +- `app_name`: Application name +- `tcb_info`: TCB measurements (MRTD, RTMRs, event log) +- `compose_hash`: Hash of the app configuration +- `app_cert`: Application certificate (PEM) -# Sign transactions, etc. -``` +### Generate TLS Certificates -### Solana +`get_tls_key()` creates fresh TLS certificates. Unlike `get_key()`, each call generates a new random key. ```python -from dstack_sdk.solana import to_keypair - -key_result = client.get_key('solana/main', 'wallet') - -# Convert to Solana keypair -keypair = to_keypair(key_result) -print(f"Solana public key: {keypair.public_key}") - -# Use with solana-py -from solana.rpc.api import Client -from solana.transaction import Transaction -from solana.system_program import transfer, TransferParams - -client_rpc = Client("https://api.mainnet-beta.solana.com") +tls = client.get_tls_key( + subject='api.example.com', + alt_names=['localhost'], + usage_ra_tls=True # Embed attestation in certificate +) -# Create and send transaction -# ... (implement transaction logic) +print(tls.key) # PEM private key +print(tls.certificate_chain) # Certificate chain ``` -## Environment Variables Encryption +**Parameters:** +- `subject` (optional): Certificate common name (e.g., domain name) +- `alt_names` (optional): List of subject alternative names +- `usage_ra_tls` (optional): Embed TDX quote in certificate extension +- `usage_server_auth` (optional): Enable for server authentication (default: `True`) +- `usage_client_auth` (optional): Enable for client authentication (default: `False`) -**Important**: This feature is specifically for **deployment-time security**, not runtime SDK operations. +**Returns:** `GetTlsKeyResponse` +- `key`: PEM-encoded private key +- `certificate_chain`: List of PEM certificates -The SDK provides end-to-end encryption capabilities for securely transmitting sensitive environment variables during dstack application deployment. When deploying applications to TEE instances, sensitive configuration data (API keys, database credentials, private keys, etc.) needs to be securely transmitted from the deployment client to the TEE application. +### Sign and Verify -### Deployment Security Problem +Sign data using TEE-derived keys (not yet released): -During application deployment, sensitive data must traverse: -1. **Client Environment** → Deployment infrastructure → **TEE Application** -2. **Risk**: Deployment infrastructure could potentially access plaintext secrets -3. **Solution**: Client-side encryption ensures only the target TEE application can decrypt secrets +```python +result = client.sign('ed25519', b'message to sign') +print(result.signature) +print(result.public_key) -### How It Works +# Verify the signature +valid = client.verify('ed25519', b'message to sign', result.signature, result.public_key) +print(valid.valid) # True +``` -1. **Pre-Deployment**: Client obtains encryption public key from KMS API -2. **Encryption**: Client encrypts environment variables using X25519 + AES-GCM -3. **Transmission**: Encrypted payload is sent through deployment infrastructure -4. **Decryption**: TEE application automatically decrypts and loads environment variables -5. **Runtime**: Application accesses secrets via standard `os.environ` +**`sign()` Parameters:** +- `algorithm`: `'ed25519'`, `'secp256k1'`, or `'secp256k1_prehashed'` +- `data`: Data to sign (bytes or string) -This ensures **true end-to-end encryption** where deployment infrastructure never sees plaintext secrets. +**`sign()` Returns:** `SignResponse` +- `signature`: Hex-encoded signature +- `public_key`: Hex-encoded public key +- `signature_chain`: Signatures proving TEE origin -### App Configuration for Encrypted Variables +**`verify()` Parameters:** +- `algorithm`: Algorithm used for signing +- `data`: Original data +- `signature`: Signature to verify +- `public_key`: Public key to verify against -Your `app-compose.json` should specify which environment variables are allowed: +**`verify()` Returns:** `VerifyResponse` +- `valid`: Boolean indicating if signature is valid -```json -{ - "manifest_version": 1, - "name": "secure-app", - "runner": "docker-compose", - "docker_compose_file": "services:\\n app:\\n build: .\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - API_KEY\\n - DATABASE_URL\\n - PRIVATE_KEY", - "allowed_envs": ["API_KEY", "DATABASE_URL", "PRIVATE_KEY"], - "kms_enabled": true -} -``` +### Emit Events -### Deployment Encryption Workflow +Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record. ```python -from dstack_sdk import encrypt_env_vars, verify_env_encrypt_public_key, EnvVar -import requests -import json - -# 1. Define sensitive environment variables -env_vars = [ - EnvVar(key='DATABASE_URL', value='postgresql://user:pass@host:5432/db'), - EnvVar(key='API_SECRET_KEY', value='your-secret-key'), - EnvVar(key='JWT_PRIVATE_KEY', value='-----BEGIN PRIVATE KEY-----\\n...'), - EnvVar(key='WALLET_MNEMONIC', value='abandon abandon abandon...'), -] - -# 2. Obtain encryption public key from KMS API (dstack-vmm or Phala Cloud) -response = requests.post('/prpc/GetAppEnvEncryptPubKey?json', - headers={'Content-Type': 'application/json'}, - json={'app_id': 'your-app-id-hex'}) -data = response.json() -public_key, signature = data['public_key'], data['signature'] - -# 3. Verify KMS API authenticity to prevent man-in-the-middle attacks -public_key_bytes = bytes.fromhex(public_key) -signature_bytes = bytes.fromhex(signature) - -trusted_pubkey = verify_env_encrypt_public_key(public_key_bytes, signature_bytes, 'your-app-id-hex') -if not trusted_pubkey: - raise RuntimeError('KMS API provided untrusted encryption key') - -print('Verified KMS public key:', trusted_pubkey.hex()) - -# 4. Encrypt environment variables for secure deployment -encrypted_data = encrypt_env_vars(env_vars, public_key) -print('Encrypted payload:', encrypted_data) - -# 5. Deploy with encrypted configuration -# deploy_dstack_app({ -# 'app_id': 'your-app-id-hex', -# 'encrypted_env': encrypted_data, -# # ... other deployment parameters -# }) +client.emit_event('config_loaded', 'production') +client.emit_event('plugin_initialized', 'auth-v2') ``` -### Security Guarantees +**Parameters:** +- `event`: Event name (string identifier) +- `payload`: Event value (bytes or string) -The environment encryption system provides several security guarantees: +## Async Client -**End-to-End Encryption**: Environment variables are encrypted on the client side and can only be decrypted by the target dstack application inside the TEE. Even the deployment infrastructure cannot access the plaintext values. +For async applications, use `AsyncDstackClient`: -**KMS Authenticity Verification**: The `verify_env_encrypt_public_key` function validates that the encryption public key comes from a trusted KMS (Key Management Service), preventing man-in-the-middle attacks during key exchange. +```python +from dstack_sdk import AsyncDstackClient +import asyncio -**Forward Secrecy**: Each encryption operation uses ephemeral X25519 keypairs, ensuring that compromising long-term keys cannot decrypt past communications. +async def main(): + client = AsyncDstackClient() -**Authenticated Encryption**: AES-256-GCM provides both confidentiality and integrity protection, detecting any tampering with encrypted data. + info = await client.info() + key = await client.get_key('wallet/eth') -## Cryptographic Security + # Concurrent operations + keys = await asyncio.gather( + client.get_key('user/alice'), + client.get_key('user/bob'), + ) -### Key Derivation Security +asyncio.run(main()) +``` -The SDK implements secure key derivation using: +## Blockchain Integration -- **Deterministic Generation**: Keys are derived using HMAC-based Key Derivation Function (HKDF) -- **Application Isolation**: Each path produces unique keys, preventing cross-application access -- **Signature Verification**: All derived keys include cryptographic proof of origin -- **TEE Protection**: Master keys never leave the secure enclave +### Ethereum ```python -# Each path generates a unique, deterministic key -wallet1 = client.get_key('app1/wallet', 'ethereum') -wallet2 = client.get_key('app2/wallet', 'ethereum') -# wallet1.key != wallet2.key (guaranteed different) +from dstack_sdk.ethereum import to_account -same_wallet = client.get_key('app1/wallet', 'ethereum') -# wallet1.key == same_wallet.key (guaranteed identical) +key = client.get_key('wallet/ethereum') +account = to_account(key) +print(account.address) ``` -### Remote Attestation - -TDX quotes provide cryptographic proof of: - -- **Code Integrity**: Measurement of loaded application code -- **Data Integrity**: Inclusion of application-specific data in quote -- **Environment Authenticity**: Verification of TEE platform and configuration +### Solana ```python -import json -import time - -application_state = json.dumps({ - "version": "1.0.0", - "config_hash": "sha256:...", - "timestamp": time.time() -}) - -quote = client.get_quote(application_state.encode()) +from dstack_sdk.solana import to_keypair -# Quote can be verified by external parties to confirm: -# 1. Application is running in genuine TEE -# 2. Application code matches expected measurements -# 3. Application state is authentic and unmodified +key = client.get_key('wallet/solana') +keypair = to_keypair(key) +print(keypair.public_key) ``` -### Environment Encryption Protocol - -The encryption scheme uses: - -- **X25519 ECDH**: Elliptic curve key exchange for forward secrecy -- **AES-256-GCM**: Authenticated encryption with 256-bit keys -- **Ephemeral Keys**: New keypair generated for each encryption operation -- **Authenticated Data**: Prevents tampering and ensures integrity - -## Development and Testing - -### Local Development +## Development -For development without physical TDX hardware: +For local development without TDX hardware, use the simulator: ```bash -# Clone and build simulator git clone https://github.com/Dstack-TEE/dstack.git cd dstack/sdk/simulator ./build.sh ./dstack-simulator - -# Set environment variable -export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 -``` - -### Testing Connectivity - -```python -client = DstackClient() - -# Check if dstack service is available -is_available = client.is_reachable() -if not is_available: - print('dstack service is not reachable') - exit(1) -``` - -## API Reference - -### Client Classes Overview - -The Python SDK provides four client classes with identical functionality but different execution models: - -| Client Class | Type | Status | Use Case | -|-------------|------|--------|----------| -| `DstackClient` | Synchronous | ✅ Active | General use, scripts, synchronous code | -| `AsyncDstackClient` | Asynchronous | ✅ Active | Web apps, concurrent operations | -| `TappdClient` | Synchronous | ⚠️ Deprecated | Legacy compatibility only | -| `AsyncTappdClient` | Asynchronous | ⚠️ Deprecated | Legacy compatibility only | - -### DstackClient (Synchronous) - -#### Constructor - -```python -DstackClient(endpoint: str | None = None) ``` -**Parameters:** -- `endpoint` (optional): Connection endpoint - - Unix socket path (production): `/var/run/dstack.sock` - - HTTP/HTTPS URL (development): `http://localhost:8090` - - Environment variable: `DSTACK_SIMULATOR_ENDPOINT` - -### AsyncDstackClient (Asynchronous) +Then set the endpoint: -#### Constructor - -```python -AsyncDstackClient(endpoint: str | None = None) +```bash +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 ``` -**Parameters:** Same as `DstackClient` - -**Key Differences from Sync Client:** -- All methods are `async` and must be awaited -- Supports concurrent operations with `asyncio.gather()` -- Better performance for multiple I/O operations -- Integrates with async frameworks (FastAPI, aiohttp, etc.) - -**Production App Configuration:** +Run tests with PDM: -The Docker Compose configuration is embedded in `app-compose.json`: - -```json -{ - "manifest_version": 1, - "name": "production-app", - "runner": "docker-compose", - "docker_compose_file": "services:\\n app:\\n image: your-app\\n volumes:\\n - /var/run/dstack.sock:/var/run/dstack.sock\\n environment:\\n - NODE_ENV=production", - "public_tcbinfo": true -} +```bash +pdm install -d +pdm run pytest -s ``` -**Important**: The `docker_compose_file` contains YAML content as a string, ensuring the volume binding for `/var/run/dstack.sock` is included. - -### Shared Methods +--- -All methods below are available in both synchronous and asynchronous clients with identical signatures and functionality: +## Deployment Utilities -| Sync Method | Async Method | Description | -|------------|--------------|-------------| -| `client.info()` | `await async_client.info()` | Get TEE instance information | -| `client.get_key(...)` | `await async_client.get_key(...)` | Derive deterministic keys | -| `client.get_quote(...)` | `await async_client.get_quote(...)` | Generate attestation quote | -| `client.get_tls_key(...)` | `await async_client.get_tls_key(...)` | Generate TLS certificate | -| `client.emit_event(...)` | `await async_client.emit_event(...)` | Log custom events | -| `client.is_reachable()` | `await async_client.is_reachable()` | Test connectivity | +These utilities are for deployment scripts, not runtime SDK operations. -#### Methods +### Encrypt Environment Variables -##### `info() -> InfoResponse` / `async info() -> InfoResponse` - -Retrieves comprehensive information about the TEE instance. - -**Returns:** `InfoResponse` -- `app_id`: Unique application identifier -- `instance_id`: Unique instance identifier -- `app_name`: Application name from configuration -- `device_id`: TEE device identifier -- `tcb_info`: Trusted Computing Base information - - `mrtd`: Measurement of TEE domain - - `rtmr0-3`: Runtime Measurement Registers - - `event_log`: Boot and runtime events - - `os_image_hash`: Operating system measurement - - `compose_hash`: Application configuration hash -- `app_cert`: Application certificate in PEM format -- `key_provider_info`: Key management configuration - -##### `get_key(path: str | None = None, purpose: str | None = None, algorithm: str = "secp256k1") -> GetKeyResponse` - -Derives a deterministic secp256k1/K256 private key for blockchain and Web3 applications. This is the primary method for obtaining cryptographic keys for wallets, signing, and other deterministic key scenarios. - -**Parameters:** -- `path`: Unique identifier for key derivation (e.g., `"wallet/ethereum"`, `"signing/solana"`) -- `purpose` (optional): Additional context for key usage (default: `""`) -- `algorithm` (optional): Key algorithm (e.g., "secp256k1", "ed25519"). Defaults to "secp256k1". - -**Returns:** `GetKeyResponse` -- `key`: 32-byte secp256k1 private key as hex string (suitable for Ethereum, Bitcoin, Solana, etc.) -- `signature_chain`: Array of cryptographic signatures proving key authenticity - -**Key Characteristics:** -- **Deterministic**: Same path + purpose always generates identical key -- **Isolated**: Different paths produce cryptographically independent keys -- **Blockchain-Ready**: Compatible with secp256k1 curve (Ethereum, Bitcoin, Solana) -- **Verifiable**: Signature chain proves key was derived inside genuine TEE - -**Use Cases:** -- Cryptocurrency wallets -- Transaction signing -- DeFi protocol interactions -- NFT operations -- Any scenario requiring consistent, reproducible keys +Encrypt secrets before deploying to dstack: ```python -# Examples of deterministic key derivation -eth_wallet = client.get_key('wallet/ethereum', 'mainnet') -btc_wallet = client.get_key('wallet/bitcoin', 'mainnet') -sol_wallet = client.get_key('wallet/solana', 'mainnet') - -# Same path always returns same key -key1 = client.get_key('my-app/signing') -key2 = client.get_key('my-app/signing') -# key1.decode_key() == key2.decode_key() (guaranteed identical) - -# Different paths return different keys -user_a = client.get_key('user/alice/wallet') -user_b = client.get_key('user/bob/wallet') -# user_a.decode_key() != user_b.decode_key() (guaranteed different) -``` - -##### `get_quote(report_data: str | bytes) -> GetQuoteResponse` - -Generates a TDX attestation quote containing the provided report data. - -**Parameters:** -- `report_data`: Data to include in quote (max 64 bytes) - -**Returns:** `GetQuoteResponse` -- `quote`: TDX quote as hex string -- `event_log`: JSON string of system events -- `replay_rtmrs()`: Method returning computed RTMR values - -**Use Cases:** -- Remote attestation of application state -- Cryptographic proof of execution environment -- Audit trail generation - -##### `get_tls_key(subject: str | None = None, alt_names: List[str] | None = None, usage_ra_tls: bool = False, usage_server_auth: bool = True, usage_client_auth: bool = False) -> GetTlsKeyResponse` - -Generates a fresh, random TLS key pair with X.509 certificate for TLS/SSL connections. **Important**: This method generates different keys on each call - use `get_key()` for deterministic keys. - -**Parameters:** -- `subject` (optional): Certificate subject (Common Name) - typically the domain name (default: `""`) -- `alt_names` (optional): Subject Alternative Names - additional domains/IPs for the certificate (default: `[]`) -- `usage_ra_tls` (optional): Include TDX attestation quote in certificate extension for remote verification (default: `False`) -- `usage_server_auth` (optional): Enable server authentication - allows certificate to authenticate servers (default: `True`) -- `usage_client_auth` (optional): Enable client authentication - allows certificate to authenticate clients (default: `False`) - -**Returns:** `GetTlsKeyResponse` -- `key`: Private key in PEM format (X.509/PKCS#8) -- `certificate_chain`: Certificate chain list - -**Key Characteristics:** -- **Random Generation**: Each call produces a completely different key -- **TLS-Optimized**: Keys and certificates designed for TLS/SSL scenarios -- **RA-TLS Support**: Optional remote attestation extension in certificates -- **TEE-Signed**: Certificates signed by TEE-resident Certificate Authority - -##### `sign(algorithm: str, data: str | bytes) -> SignResponse` - -Signs data using a derived key. - -**Parameters**: -- `algorithm`: The algorithm to use (e.g., "ed25519", "secp256k1", "secp256k1_prehashed"). -- `data`: The data to sign. If using "secp256k1_prehashed", this must be a 32-byte hash (bytes). - -**Returns**: `SignResponse` -- `signature`: The resulting signature as hex string. -- `signature_chain`: List of hex strings proving key authenticity. -- `public_key`: The public key corresponding to the derived signing key as hex string. - -##### `verify(algorithm: str, data: str | bytes, signature: str | bytes, public_key: str | bytes) -> VerifyResponse` - -Verifies a payload signature. - -**Parameters**: -- `algorithm`: The algorithm used for signing. -- `data`: The original data that was signed. -- `signature`: The signature to verify (hex string or bytes). -- `public_key`: The public key to use for verification (hex string or bytes). - -**Returns**: `VerifyResponse` -- `valid`: A bool indicating if the signature is valid. - -##### `emit_event(event: str, payload: str | bytes) -> None` - -Extends RTMR3 with a custom event for audit logging. - -**Parameters:** -- `event`: Event identifier string -- `payload`: Event data - -**Requirements:** -- dstack OS version 0.5.0 or later -- Events are permanently recorded in TEE measurements - -##### `is_reachable() -> bool` - -Tests connectivity to the dstack service. - -**Returns:** `bool` indicating service availability - -## Utility Functions - -### Compose Hash Calculation - -```python -from dstack_sdk import get_compose_hash +from dstack_sdk import encrypt_env_vars, verify_env_encrypt_public_key, EnvVar -app_compose = { - "manifest_version": 1, - "name": "my-app", - "runner": "docker-compose", - "docker_compose_file": "docker-compose.yml" -} +# Get and verify the KMS public key +# (obtain public_key and signature from KMS API) +kms_identity = verify_env_encrypt_public_key(public_key_bytes, signature_bytes, app_id) +if not kms_identity: + raise RuntimeError('Invalid KMS key') -hash_value = get_compose_hash(app_compose) -print('Configuration hash:', hash_value) +# Encrypt variables +env_vars = [ + EnvVar(key='DATABASE_URL', value='postgresql://...'), + EnvVar(key='API_KEY', value='secret'), +] +encrypted = encrypt_env_vars(env_vars, public_key) ``` -### KMS Public Key Verification - -Verify the authenticity of encryption public keys provided by KMS APIs: +### Calculate Compose Hash ```python -from dstack_sdk import verify_env_encrypt_public_key - -# Example: Verify KMS-provided encryption key -public_key = bytes.fromhex('e33a1832c6562067ff8f844a61e51ad051f1180b66ec2551fb0251735f3ee90a') -signature = bytes.fromhex('8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00') -app_id = '0000000000000000000000000000000000000000' - -kms_identity = verify_env_encrypt_public_key(public_key, signature, app_id) +from dstack_sdk import get_compose_hash -if kms_identity: - print('Trusted KMS identity:', kms_identity.hex()) - # Safe to use the public key for encryption -else: - print('KMS signature verification failed') - # Potential man-in-the-middle attack +hash_value = get_compose_hash(app_compose_dict) ``` -## Security Best Practices - -1. **Key Management** - - Use descriptive, unique paths for key derivation - - Never expose derived keys outside the TEE - - Implement proper access controls in your application - -2. **Remote Attestation** - - Always verify quotes before trusting remote TEE instances - - Include application-specific data in quote generation - - Validate RTMR measurements against expected values - -3. **TLS Configuration** - - Enable RA-TLS for attestation-based authentication - - Use appropriate certificate validity periods - - Implement proper certificate validation - -4. **Error Handling** - - Handle cryptographic operation failures gracefully - - Log security events for monitoring - - Implement fallback mechanisms where appropriate - -## Migration Guide - -### Critical API Changes: Understanding the Separation - -The legacy client mixed two different use cases that have now been properly separated: - -1. **`get_key()`**: Deterministic key derivation for Web3/blockchain (secp256k1) -2. **`get_tls_key()`**: Random TLS certificate generation for HTTPS/SSL - -### From TappdClient to DstackClient - -**⚠️ BREAKING CHANGE**: `TappdClient` and `AsyncTappdClient` are deprecated and will be removed. All users must migrate to `DstackClient` and `AsyncDstackClient`. - -### Complete Migration Reference +--- -| Component | TappdClient (Old) | DstackClient (New) | Status | -|-----------|-------------------|-------------------|---------| -| **Socket Path** | `/var/run/tappd.sock` | `/var/run/dstack.sock` | ✅ Updated | -| **HTTP URL Format** | `http://localhost/prpc/Tappd.` | `http://localhost/` | ✅ Simplified | -| **K256 Key Method** | `get_key(...)` | `get_key(...)` | ✅ Same | -| **TLS Certificate Method** | `derive_key(...)` | `get_tls_key(...)` | ✅ Separated | -| **TDX Quote (Raw)** | `tdx_quote(...)` | `get_quote(report_data)` | ✅ Renamed | +## Migration from TappdClient -#### Migration Steps - -**Step 1: Update Imports and Client** +Replace `TappdClient` with `DstackClient`: ```python # Before -from dstack_sdk import TappdClient, AsyncTappdClient +from dstack_sdk import TappdClient client = TappdClient() -async_client = AsyncTappdClient() -# After -from dstack_sdk import DstackClient, AsyncDstackClient +# After +from dstack_sdk import DstackClient client = DstackClient() -async_client = AsyncDstackClient() ``` -**Step 2: Update Method Calls** - -```python -# For deterministic keys (most common) -# Before: TappdClient methods -key_result = client.get_key('wallet', 'ethereum') - -# After: DstackClient methods (same!) -key_result = client.get_key('wallet', 'ethereum') - -# For TLS certificates -# Before: derive_key with TLS options -tls_cert = client.derive_key('api', 'example.com', ['localhost']) - -# After: get_tls_key with proper options -tls_cert = client.get_tls_key( - subject='example.com', - alt_names=['localhost'] -) -``` - -### Migration Checklist - -- [ ] **Infrastructure Updates:** - - [ ] Update Docker volume binding to `/var/run/dstack.sock` - - [ ] Change environment variables from `TAPPD_*` to `DSTACK_*` - -- [ ] **Client Code Updates:** - - [ ] Replace `TappdClient` with `DstackClient` - - [ ] Replace `AsyncTappdClient` with `AsyncDstackClient` - - [ ] Replace `derive_key()` calls with `get_tls_key()` for TLS certificates - - [ ] Replace `tdx_quote()` calls with `get_quote()` - -- [ ] **Testing:** - - [ ] Test that deterministic keys still work as expected - - [ ] Verify TLS certificate generation works - - [ ] Test quote generation with new interface - -## Development - -We use [PDM](https://pdm-project.org/en/latest/) for local development and creating an isolated environment. - -To initiate development: - -```bash -pdm install -d -``` - -To run tests: - -```bash -DSTACK_SIMULATOR_ENDPOINT=/path/to/dstack/sdk/simulator/dstack.sock pdm run pytest -s -``` +Method changes: +- `derive_key()` → `get_tls_key()` for TLS certificates +- `tdx_quote()` → `get_quote()` +- Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` ## License diff --git a/sdk/rust/README.md b/sdk/rust/README.md index 2ae47ad1..a41e2cd2 100644 --- a/sdk/rust/README.md +++ b/sdk/rust/README.md @@ -1,6 +1,6 @@ -# dstack Crate +# dstack SDK for Rust -This crate provides rust clients for communicating with both the current dstack server and the legacy tappd service, which are available inside dstack. +Access TEE features from your Rust application running inside dstack. Derive deterministic keys, generate attestation quotes, create TLS certificates, and sign data—all backed by hardware security. ## Installation @@ -9,161 +9,191 @@ This crate provides rust clients for communicating with both the current dstack dstack-sdk = { git = "https://github.com/Dstack-TEE/dstack.git" } ``` -## Basic Usage - -### DstackClient (Current API) +## Quick Start ```rust use dstack_sdk::dstack_client::DstackClient; #[tokio::main] async fn main() -> Result<(), Box> { - let client = DstackClient::new(None); // Uses env var or default to Unix socket - - // Get system info - let info = client.info().await?; - println!("Instance ID: {}", info.instance_id); - - // Derive a key - let key_resp = client.get_key(Some("my-app".to_string()), None).await?; - println!("Key: {}", key_resp.key); - println!("Signature Chain: {:?}", key_resp.signature_chain); + let client = DstackClient::new(None); - // Generate TDX quote - let quote_resp = client.get_quote(b"test-data".to_vec()).await?; - println!("Quote: {}", quote_resp.quote); - let rtmrs = quote_resp.replay_rtmrs()?; - println!("Replayed RTMRs: {:?}", rtmrs); + // Derive a deterministic key for your wallet + let key = client.get_key(Some("wallet/eth".to_string()), None).await?; + println!("{}", key.key); // Same path always returns the same key - // Emit an event - client.emit_event("BootComplete".to_string(), b"payload-data".to_vec()).await?; + // Generate an attestation quote + let quote = client.get_quote(b"my-app-state".to_vec()).await?; + println!("{}", quote.quote); Ok(()) } ``` -### TappdClient (Legacy API) +The client automatically connects to `/var/run/dstack.sock`. For local development with the simulator: ```rust -use dstack_sdk::tappd_client::TappdClient; +let client = DstackClient::new(Some("http://localhost:8090".to_string())); +``` -#[tokio::main] -async fn main() -> Result<(), Box> { - let client = TappdClient::new(None); // Uses env var or default to Unix socket - - // Get system info - let info = client.info().await?; - println!("Instance ID: {}", info.instance_id); - println!("App Name: {}", info.app_name); - - // Derive a key - let key_resp = client.derive_key("my-app").await?; - println!("Key: {}", key_resp.key); - println!("Certificate Chain: {:?}", key_resp.certificate_chain); - - // Decode the key to bytes (extracts raw ECDSA P-256 private key - 32 bytes) - let key_bytes = key_resp.to_bytes()?; - println!("ECDSA P-256 private key bytes (32 bytes): {:?}", key_bytes.len()); - - // Generate quote (exactly 64 bytes of report data required) - let mut report_data = b"test-data".to_vec(); - report_data.resize(64, 0); // Pad to 64 bytes - let quote_resp = client.get_quote(report_data).await?; - println!("Quote: {}", quote_resp.quote); - let rtmrs = quote_resp.replay_rtmrs()?; - println!("Replayed RTMRs: {:?}", rtmrs); +## Core API - Ok(()) -} +### Derive Keys + +`get_key()` derives deterministic keys bound to your application's identity (`app_id`). The same path always produces the same key for your app, but different apps get different keys even with the same path. + +```rust +// Derive keys by path +let eth_key = client.get_key(Some("wallet/ethereum".to_string()), None).await?; +let btc_key = client.get_key(Some("wallet/bitcoin".to_string()), None).await?; + +// Use path to separate keys +let mainnet_key = client.get_key(Some("wallet/eth/mainnet".to_string()), None).await?; +let testnet_key = client.get_key(Some("wallet/eth/testnet".to_string()), None).await?; ``` -## Features +**Parameters:** +- `path`: Key derivation path (determines the key) +- `purpose` (optional): Included in signature chain message, does not affect the derived key + +**Returns:** `GetKeyResponse` +- `key`: Hex-encoded private key +- `signature_chain`: Signatures proving the key was derived in a genuine TEE -### DstackClient Initialization +### Generate Attestation Quotes + +`get_quote()` creates a TDX quote proving your code runs in a genuine TEE. ```rust -let client = DstackClient::new(Some("http://localhost:8000")); +let quote = client.get_quote(b"user:alice:nonce123".to_vec()).await?; + +// Replay RTMRs from the event log +let rtmrs = quote.replay_rtmrs()?; +println!("{:?}", rtmrs); ``` -- `endpoint`: Optional HTTP URL or Unix socket path (`/var/run/dstack.sock` by default) -- Will use the `DSTACK_SIMULATOR_ENDPOINT` environment variable if set -### TappdClient Initialization (Legacy API) +**Parameters:** +- `report_data`: Exactly 64 bytes recommended. If shorter, pad with zeros. If longer, hash it first (e.g., SHA-256). + +**Returns:** `GetQuoteResponse` +- `quote`: Hex-encoded TDX quote +- `event_log`: JSON string of measured events +- `replay_rtmrs()`: Method to compute RTMR values from event log + +### Get Instance Info ```rust -let client = TappdClient::new(Some("/var/run/tappd.sock")); +let info = client.info().await?; +println!("{}", info.app_id); +println!("{}", info.instance_id); +println!("{}", info.tcb_info); ``` -- `endpoint`: Optional HTTP URL or Unix socket path (`/var/run/tappd.sock` by default) -- Will use the `TAPPD_SIMULATOR_ENDPOINT` environment variable if set -- Supports the legacy tappd.sock API for backwards compatibility -## API Methods +**Returns:** `InfoResponse` +- `app_id`: Application identifier +- `instance_id`: Instance identifier +- `app_name`: Application name +- `tcb_info`: TCB measurements (JSON string) +- `compose_hash`: Hash of the app configuration +- `app_cert`: Application certificate (PEM) + +### Generate TLS Certificates -### DstackClient Methods +`get_tls_key()` creates fresh TLS certificates. Unlike `get_key()`, each call generates a new random key. -#### `info(): InfoResponse` -Fetches metadata and measurements about the CVM instance. +```rust +use dstack_sdk_types::dstack::TlsKeyConfig; -#### `get_key(path: Option, purpose: Option) -> GetKeyResponse` -Derives a key for a specified path and optional purpose. -- `key`: Private key in hex format -- `signature_chain`: Vec of X.509 certificate chain entries +let tls_config = TlsKeyConfig::builder() + .subject("api.example.com") + .alt_names(vec!["localhost".to_string()]) + .usage_ra_tls(true) // Embed attestation in certificate + .usage_server_auth(true) + .build(); -#### `get_quote(report_data: Vec) -> GetQuoteResponse` -Generates a TDX quote with a custom 64-byte payload. -- `quote`: Hex-encoded quote -- `event_log`: Serialized list of events -- `replay_rtmrs()`: Reconstructs RTMR values from the event log +let tls = client.get_tls_key(tls_config).await?; + +println!("{}", tls.key); // PEM private key +println!("{:?}", tls.certificate_chain); // Certificate chain +``` -#### `emit_event(event: String, payload: Vec)` -Sends an event log with associated binary payload to the runtime. +**TlsKeyConfig Options:** +- `.subject(name)`: Certificate common name (e.g., domain name) +- `.alt_names(names)`: List of subject alternative names +- `.usage_ra_tls(bool)`: Embed TDX quote in certificate extension +- `.usage_server_auth(bool)`: Enable for server authentication +- `.usage_client_auth(bool)`: Enable for client authentication -#### `get_tls_key(...) -> GetTlsKeyResponse` -Requests a key and X.509 certificate chain for RA-TLS or server/client authentication. +**Returns:** `GetTlsKeyResponse` +- `key`: PEM-encoded private key +- `certificate_chain`: List of PEM certificates -#### sign(algorithm: &str, data: Vec) -> SignResponse -Signs a payload using a derived key. +### Sign and Verify -#### verify(algorithm: &str, data: Vec, signature: Vec, public_key: Vec) -> VerifyResponse -Verifies a payload signature. +Sign data using TEE-derived keys (not yet released): -### TappdClient Methods (Legacy API) +```rust +let result = client.sign("ed25519", b"message to sign".to_vec()).await?; +println!("{:?}", result.signature); +println!("{:?}", result.public_key); + +// Verify the signature +let valid = client.verify( + "ed25519", + b"message to sign".to_vec(), + result.signature.clone(), + result.public_key.clone() +).await?; +println!("{}", valid.valid); // true +``` -#### `info(): TappdInfoResponse` -Fetches metadata and measurements about the CVM instance. +**`sign()` Parameters:** +- `algorithm`: `"ed25519"`, `"secp256k1"`, or `"secp256k1_prehashed"` +- `data`: Data to sign -#### `derive_key(path: &str) -> DeriveKeyResponse` -Derives a key for a specified path. -- `key`: ECDSA P-256 private key in PEM format -- `certificate_chain`: Vec of X.509 certificate chain entries -- `to_bytes()`: Extracts and returns the raw ECDSA P-256 private key bytes (32 bytes) +**`sign()` Returns:** `SignResponse` +- `signature`: Signature bytes +- `public_key`: Public key bytes +- `signature_chain`: Signatures proving TEE origin -#### `derive_key_with_subject(path: &str, subject: &str) -> DeriveKeyResponse` -Derives a key with a custom certificate subject. +**`verify()` Parameters:** +- `algorithm`: Algorithm used for signing +- `data`: Original data +- `signature`: Signature to verify +- `public_key`: Public key to verify against -#### `derive_key_with_subject_and_alt_names(path: &str, subject: Option<&str>, alt_names: Option>) -> DeriveKeyResponse` -Derives a key with full certificate customization. +**`verify()` Returns:** `VerifyResponse` +- `valid`: Boolean indicating if signature is valid -#### `get_quote(report_data: Vec) -> TdxQuoteResponse` -Generates a TDX quote with exactly 64 bytes of raw report data. +### Emit Events -### Structures -- `GetKeyResponse`: Holds derived key and signature chain +Extend RTMR3 with custom measurements for your application's boot sequence (requires dstack OS 0.5.0+). These measurements are append-only and become part of the attestation record. -- `GetQuoteResponse`: Contains the TDX quote and event log, with RTMR replay support +```rust +client.emit_event("config_loaded".to_string(), b"production".to_vec()).await?; +client.emit_event("plugin_initialized".to_string(), b"auth-v2".to_vec()).await?; +``` -- `InfoResponse`: CVM instance metadata, including image and runtime measurements +**Parameters:** +- `event`: Event name (string identifier) +- `payload`: Event value (bytes) -- `SignResponse`: Holds a signature, signature chain, and public key +## Blockchain Integration -- `VerifyResponse`: Holds a boolean valid result +### Ethereum with Alloy -## API Reference +```rust +use dstack_sdk::dstack_client::DstackClient; +use dstack_sdk::ethereum::to_account; -### Running the Simulator +let key = client.get_key(Some("wallet/ethereum".to_string()), None).await?; +let signer = to_account(&key)?; +println!("Ethereum address: {}", signer.address()); +``` -For local development without TDX devices, you can use the simulator under `sdk/simulator`. +## Development -Run the simulator with: +For local development without TDX hardware, use the simulator: ```bash git clone https://github.com/Dstack-TEE/dstack.git @@ -171,25 +201,43 @@ cd dstack/sdk/simulator ./build.sh ./dstack-simulator ``` -Set the endpoint in your environment: +Then set the endpoint: + +```bash +export DSTACK_SIMULATOR_ENDPOINT=http://localhost:8090 ``` -export DSTACK_SIMULATOR_ENDPOINT=/path/to/dstack-simulator/dstack.sock + +Run examples: + +```bash +cargo run --example dstack_client_usage ``` -## Examples +--- -See the `examples/` directory for comprehensive usage examples: +## Migration from TappdClient -- `examples/dstack_client_usage.rs` - Complete example using the current DstackClient API -- `examples/tappd_client_usage.rs` - Complete example using the legacy TappdClient API +Replace `TappdClient` with `DstackClient`: -Run examples with: -```bash -cargo run --example dstack_client_usage -cargo run --example tappd_client_usage +```rust +// Before +use dstack_sdk::tappd_client::TappdClient; +let client = TappdClient::new(None); + +// After +use dstack_sdk::dstack_client::DstackClient; +let client = DstackClient::new(None); ``` +Method changes: +- `derive_key()` → `get_tls_key()` for TLS certificates +- Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` + +## Known Issues + +See [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for current limitations and workarounds. + ## License -Apache License +Apache License 2.0 From 1dd6d62a1d64d2e8ed3ca42c25e5653d34a3c592 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Tue, 30 Dec 2025 00:49:05 +0000 Subject: [PATCH 06/16] docs: add consolidated confidential AI guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate inference, training, and agents docs into a single confidential-ai.md guide. Covers the four protection layers (TLS in CVM, CPU memory, GPU memory, disk encryption), includes mermaid diagrams, and links to working examples. Also update WRITING_GUIDE.md with generic documentation principles. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .agent/WRITING_GUIDE.md | 145 ++++++++++++++++------- docs/confidential-ai.md | 251 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+), 45 deletions(-) create mode 100644 docs/confidential-ai.md diff --git a/.agent/WRITING_GUIDE.md b/.agent/WRITING_GUIDE.md index 197f9d81..aaf28fef 100644 --- a/.agent/WRITING_GUIDE.md +++ b/.agent/WRITING_GUIDE.md @@ -2,58 +2,15 @@ Guidelines for writing dstack documentation, README, and marketing content. -## Messaging - -- **Primary keyword**: "Confidential AI" — use in tagline and first paragraph -- **Secondary keyword**: "Private AI" — use in explanatory context -- **Key differentiator**: NVIDIA Confidential Computing support (H100, Blackwell GPUs) -- **Base images**: `dstack-nvidia-0.5.x` for GPU TEE support - ## Writing Style -- **Don't over-explain** why a framework is needed — assert dstack as the solution, hint at alternatives being insufficient -- **Avoid analogies as taglines** (e.g., "X for Y") — dstack is a new category, not a better version of something else +- **Don't over-explain** why a framework is needed — assert the solution, hint at alternatives being insufficient +- **Avoid analogies as taglines** (e.g., "X for Y") — if it's a new category, don't frame it as a better version of something else - **Problem → Solution flow** without explicit labels like "The problem:" or "The solution:" - **Demonstrate features through actions**, not parenthetical annotations - Bad: "Generates quotes (enabling *workload identity*)" - Good: "Generates TDX attestation quotes so users can verify exactly what's running" -## Architecture Descriptions - -When describing components for security researchers: -- Explain **what each component does** (actions) -- Show **how features emerge** from the architecture naturally -- Map components to security properties: - - Guest Agent → workload identity, key isolation, disk encryption - - KMS → code governance, key derivation bound to attested identity - - Gateway → encrypted networking, RA-TLS - - VMM → Docker Compose native, reproducible OS, GPU allocation - -## Target Audiences - -**Developers** care about: -- Easy onboarding (Docker Compose native) -- No code changes required -- Existing workflow compatibility - -**Security researchers** care about: -- Trust model (what's trusted, what's not) -- How attestation proves code integrity -- How key management prevents operator access -- How code governance is enforced on-chain - -## Feature-to-Component Mapping - -| Feature | Component | How it works | -|---------|-----------|--------------| -| Workload identity | Guest Agent | TDX attestation quotes | -| Isolated keys | Guest Agent + KMS | Per-app key derivation bound to attested identity | -| Encrypted by default | Guest Agent (disk) + Gateway (TLS) | Ephemeral disk keys, RA-TLS | -| Code governance | KMS | On-chain smart contract policies | -| Docker Compose native | VMM | Direct parsing, no translation | -| Reproducible OS | VMM | Deterministic image builds | -| Confidential GPUs | VMM + hardware | NVIDIA H100/Blackwell allocation | - ## Procedural Documentation (Guides & Tutorials) ### Test Before You Document @@ -80,3 +37,101 @@ When describing components for security researchers: - Link to prerequisite guides (don't repeat content) - Link to detailed guides for optional deep-dives - Use anchor links for specific sections when possible + +## Security Documentation + +### Trust Model Framing + +**Distinguish trust from verification:** +- "Trust" = cannot be verified, must assume correct (e.g., hardware) +- "Verify" = can be cryptographically proven (e.g., measured software) + +**Correct framing:** +- Bad: "You must trust the OS" (when it's verifiable) +- Good: "The OS is measured during boot and recorded in the attestation quote. You verify it by..." + +### Limitations: Be Honest, Not Alarmist + +State limitations plainly without false mitigations: +- Bad: "X is a single point of failure. Mitigate by running your own X." +- Good: "X is protected by [mechanism]. Like all [category] systems, [inherent limitation]. We are developing [actual solution] to address this." + +Don't suggest mitigations that don't actually help. If something is an inherent limitation of the technology, say so. + +## Documentation Quality Checklist + +From doc-requirements.md: + +1. **No bullet point walls** — Max 3-5 bullets before breaking with prose +2. **No redundancy** — Don't present same info from opposite perspectives +3. **Conversational language** — Write like explaining to a peer +4. **Short paragraphs** — Max 4 sentences per paragraph +5. **Lead with key takeaway** — First sentence tells reader why this matters +6. **Active voice** — "TEE encrypts memory" not "Memory is encrypted by TEE" +7. **Minimal em-dashes** — Max 1-2 per page, replace with "because", "so", or separate sentences + +### Redundancy Patterns to Avoid + +These often say the same thing: +- "What we protect against" + "What you don't need to trust" +- "Security guarantees" + "What attestation proves" + +Combine into single sections. One detailed explanation, brief references elsewhere. + +## README Structure + +### Order Matters +- **Quick Start before Prerequisites** — Lead with what it does, not setup +- **How It Works after Quick Start** — Users want to run it first, understand later +- Cleanup at the end, Further Reading last + +### Don't Duplicate +- Link to conceptual docs instead of repeating content +- If an overview README duplicates an example README, cut the overview +- One detailed explanation, brief references elsewhere + +### Remove Unrealistic Sections +- If most users can't actually do something (e.g., run locally without special hardware), don't include it +- Don't document workflows that require resources users don't have + +### Match the Workflow to the User +- Use tools your audience already knows (e.g., Jupyter for ML practitioners) +- Prefer official/existing images when they exist — don't reinvent +- Make the correct path the default, mention alternatives briefly + +## Code Examples + +### Question Every Snippet +- Does this code actually demonstrate something meaningful? +- Would a reader understand what it does without the prose? +- `do_thing(b"magic-string")` means nothing — show real use or remove it + +### Diagrams +- Mermaid over ASCII art — GitHub renders it nicely +- Keep diagrams simple — 3-5 nodes max +- Label edges with actions, not just arrows + +## Conciseness + +### Less is More +- 30 lines beats 150 if it says the same thing +- Cut sections that don't help users accomplish their goal +- Tables for reference, prose for explanation — don't over-table + +### Performance and Benchmarks +- One memorable number + link to full report +- Don't overwhelm with data the reader didn't ask for + +### Reader-First Writing +- Ask "what does the reader want to know?" not "what do I want to say?" +- If a section answers a question nobody asked, cut it + +## Maintenance + +### Consistency Checks +- After terminology changes, grep for related terms across all files +- Use correct industry/vendor terminology (e.g., "Confidential Computing" not "Encrypted Computing") + +### Clean Up Old Files +- When approach changes, delete orphaned files (old scripts, Dockerfiles) +- Don't leave artifacts from previous implementations diff --git a/docs/confidential-ai.md b/docs/confidential-ai.md new file mode 100644 index 00000000..5d2ad350 --- /dev/null +++ b/docs/confidential-ai.md @@ -0,0 +1,251 @@ +# Confidential AI + +Run AI workloads where the infrastructure operator can't see your data. dstack uses Intel TDX and NVIDIA Confidential Computing to encrypt everything in memory—your prompts, model weights, and intermediate computations stay private. + +## What Makes It Confidential + +Four things need to be true for the system to be actually confidential: + +**TLS terminates inside the VM.** Your HTTPS connection ends inside the Confidential VM, not at some load balancer outside. The operator never sees plaintext traffic. + +**CPU memory is encrypted.** Intel TDX encrypts all RAM with hardware keys. The hypervisor can't read it, the host OS can't read it, even physical access to the DIMM won't help. + +**GPU memory is encrypted.** On H100/H200/Blackwell with NVIDIA CC mode, GPU memory is encrypted too. Model weights and activations stay protected during inference and training. + +**Disk is encrypted.** Anything written to storage uses keys derived from the TEE. The storage backend only sees ciphertext. + +When all four are in place, data stays encrypted from network ingress to egress. + +## Private Inference + +Deploy vLLM behind a signing proxy. Users can verify responses came from your TEE, not a compromised server. + +```yaml +services: + vllm: + image: vllm/vllm-openai:latest + command: --model Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 + deploy: + resources: + reservations: + devices: + - driver: nvidia + capabilities: [gpu] + + proxy: + image: phalanetwork/vllm-proxy:latest + ports: + - "8000:8000" + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + environment: + - VLLM_BASE_URL=http://vllm:8000 + - MODEL_NAME=Qwen/Qwen2.5-7B-Instruct + - TOKEN=${TOKEN} +``` + +The data flow looks like this: + +```mermaid +graph LR + User -->|TLS| Proxy + subgraph TEE[Confidential VM] + Proxy[vllm-proxy] + Proxy -->|HTTP| vLLM + vLLM --> GPU[GPU Memory] + Proxy -.->|signs with| Key[TEE-derived key] + end + Proxy -->|Response + Signature| User +``` + +The proxy terminates TLS, forwards to vLLM, and signs responses with a key derived from the TEE's identity. That signature proves the response came from this specific environment. + +Deploy it: + +```bash +phala deploy -n my-llm -c docker-compose.yaml \ + --instance-type h200.small \ + -e TOKEN=your-secret +``` + +Verify a response signature: + +```python +import requests + +sig = requests.get( + f"https://your-endpoint/v1/signature/{chat_id}", + headers={"Authorization": "Bearer your-token"} +).json() + +# sig contains signing_address, response_hash, signature +``` + +Try it live at [chat.redpill.ai](https://chat.redpill.ai). Full example: [dstack-examples/ai/inference](https://github.com/Dstack-TEE/dstack-examples/tree/main/ai/inference). + +## Confidential Training + +Fine-tune on sensitive data without exposing it to the operator. The training data and resulting weights stay in encrypted memory. + +```yaml +services: + trainer: + image: unsloth/unsloth:latest + command: python train.py --model meta-llama/Llama-3.2-3B --data /data/dataset.jsonl + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + - training-data:/data + environment: + - HF_TOKEN=${HF_TOKEN} + - WANDB_API_KEY=${WANDB_API_KEY} + deploy: + resources: + reservations: + devices: + - driver: nvidia + capabilities: [gpu] + +volumes: + training-data: +``` + +With NVIDIA CC on H100/H200, GPU memory is encrypted. Weights, gradients, and activations stay confidential throughout training. + +```mermaid +graph TB + subgraph TEE[Confidential VM] + Data[Training Data] --> Trainer + Trainer --> GPU[Confidential GPU Memory] + GPU --> Weights[Model Weights] + end + External[Data Provider] -->|encrypted upload| Data + Weights -->|encrypted download| External +``` + +Before a data provider shares sensitive data, they should: + +1. Review your docker-compose.yaml—check for unexpected network access or volume mounts +2. Get the attestation quote and verify the compose hash matches +3. Confirm it's running on genuine TDX + NVIDIA CC hardware + +The compose hash is a hash of your docker-compose.yaml. It's included in the attestation quote, so data providers can verify exactly what code will process their data. + +```python +from dstack_sdk import DstackClient + +client = DstackClient() +info = client.info() + +print(f"Compose hash: {info.compose_hash}") +# Data provider compares this against the docker-compose they reviewed +``` + +Full example: [dstack-examples/ai/training](https://github.com/Dstack-TEE/dstack-examples/tree/main/ai/training). + +## Trustworthy AI Agents + +Agents do complex things with user data—RAG lookups, database queries, LLM calls. Users want guarantees: their data won't be logged, won't be used for training, won't be exfiltrated. + +For true end-to-end privacy, the agent should call a confidential LLM endpoint (like the vllm-proxy from the inference section) instead of a third-party API. This keeps user prompts encrypted the entire way. + +```yaml +services: + agent: + image: your-agent:latest + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + environment: + # Point to a confidential LLM endpoint, not OpenAI + - LLM_BASE_URL=https://api.redpill.ai/v1 + - LLM_API_KEY=${LLM_API_KEY} + - DATABASE_URL=${DATABASE_URL} + ports: + - "8080:8080" +``` + +The architecture looks like this: + +```mermaid +graph TB + User -->|TLS| Agent + subgraph TEE1[Agent CVM] + Agent[Agent Code] + Agent --> RAG[RAG Pipeline] + Agent --> DB[(Database)] + end + Agent -->|TLS| LLM + subgraph TEE2[LLM CVM] + LLM[vllm-proxy + vLLM] + end + Agent -->|response| User + Operator -.->|cannot access| TEE1 + Operator -.->|cannot access| TEE2 +``` + +Both the agent and the LLM run in separate TEEs. User queries stay encrypted from browser to agent to LLM and back. The operator sees nothing. + +**Data stays private.** RAG retrievals, database queries, LLM calls, and agent state all run in encrypted memory. Using a confidential LLM means prompts never leave the encrypted environment. + +**No hidden behavior.** The compose hash in the attestation proves what code is running. Users can audit the code and verify it matches—no secret logging, no unauthorized data collection. + +**Provable constraints.** Want to prove your agent doesn't send user data to training pipelines? The auditable code + attestation makes that verifiable. + +For agents that need persistent keys (signing, encryption), derive them from the TEE: + +```python +from dstack_sdk import DstackClient + +client = DstackClient() +key = client.get_key('agent/signing-key') +# Same deployment = same key, but key never leaves TEE +``` + +Full example: [dstack-examples/ai/agents](https://github.com/Dstack-TEE/dstack-examples/tree/main/ai/agents). + +## Verification + +Attestation proves the hardware is genuine and shows what code is running. But it doesn't automatically prove the code is safe—you need to audit it yourself. + +```mermaid +graph LR + App[Your App] -->|quote| Verifier + Verifier -->|valid + compose_hash| You + You -->|compare| Compose[docker-compose.yaml] + Compose -->|audit| Code[Source Code] +``` + +The verification flow: + +```python +import requests + +# Get attestation with a nonce to prevent replay +attestation = requests.get( + "https://your-app/attestation", + params={"nonce": "random-challenge"} +).json() + +# Compare compose hash against what you reviewed +expected_hash = "sha256:abc123..." # From your audited docker-compose +assert attestation["compose_hash"] == expected_hash +``` + +For visual verification, paste the quote into [proof.t16z.com](https://proof.t16z.com). + +The compose hash only tells you *what* is running. You still need to verify that code does what you expect—no secret logging, no data exfiltration, proper access controls. + +## Performance + +GPU inference runs at 99% of bare-metal speed on H100/H200. The memory encryption happens in hardware, so the overhead is minimal. Larger models actually perform better in TEE—the encryption cost is fixed while compute scales with model size. + +See the [full benchmark report](https://docs.phala.network/dstack/phala-cloud/references/performance-report) for CPU and storage numbers. + +## Getting Started + +1. Try [chat.redpill.ai](https://chat.redpill.ai) to see private inference in action +2. Deploy your own with the [inference example](https://github.com/Dstack-TEE/dstack-examples/tree/main/ai/inference) +3. Read the [Security Model](./security-model.md) for the full threat model + +## Production + +redpill.ai and NEAR AI run confidential AI on dstack in production. From 666720fb559e4ca2b5dd688717fc21ca19edbd16 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Tue, 30 Dec 2025 00:58:10 +0000 Subject: [PATCH 07/16] docs: update README links for consolidated docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use Cases: point to single confidential-ai.md instead of separate inference.md, agents.md, training.md - Fix security.md → security-model.md (file was renamed) - Add security-best-practices.md to Reference section - Delete old docs: inference.md, agents.md, training.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 9 +- docs/agents.md | 274 ---------------------------------------- docs/inference.md | 112 ----------------- docs/training.md | 310 ---------------------------------------------- 4 files changed, 4 insertions(+), 701 deletions(-) delete mode 100644 docs/agents.md delete mode 100644 docs/inference.md delete mode 100644 docs/training.md diff --git a/README.md b/README.md index 83830cf1..fc355b45 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Your container runs inside a Confidential VM (Intel TDX) with optional GPU isola - **VMM**: Runs on bare-metal TDX hosts. Parses docker-compose files directly — no app changes needed. Boots CVMs from a reproducible OS image. Allocates CPU, memory, and confidential GPU resources. -[Full security model →](./docs/security.md) +[Full security model →](./docs/security-model.md) ## SDKs @@ -91,9 +91,7 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use ## Documentation **Use Cases** -- [Private Inference](./docs/inference.md) - vLLM with attestation -- [Secure Agents](./docs/agents.md) - LangChain/Claude SDK with protected credentials -- [Confidential Training](./docs/training.md) - Fine-tuning on sensitive data +- [Confidential AI](./docs/confidential-ai.md) - Inference, agents, and training with hardware privacy **Guides** - [Usage Guide](./docs/usage.md) - Deploying and managing apps @@ -102,7 +100,8 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use - [VMM CLI Guide](./docs/vmm-cli-user-guide.md) - Command-line deployment **Reference** -- [Security](./docs/security.md) - Threat model and best practices +- [Security Model](./docs/security-model.md) - Threat model and trust boundaries +- [Security Best Practices](./docs/security-best-practices.md) - Production hardening - [CVM Boundaries](./docs/cvm-boundaries.md) - Information exchange and isolation - [App Compose Format](./docs/normalized-app-compose.md) - Compose file specification - [Gateway](./docs/dstack-gateway.md) - Gateway configuration diff --git a/docs/agents.md b/docs/agents.md deleted file mode 100644 index 2e52f977..00000000 --- a/docs/agents.md +++ /dev/null @@ -1,274 +0,0 @@ -# Secure Agents & RAG - -Run AI agents with secure key management, protected API credentials, and verifiable execution. - -## Why TEE for Agents? - -Autonomous agents need: -- **Private keys** for blockchain transactions -- **API credentials** for external services (OpenAI, databases, etc.) -- **Sensitive data** in RAG pipelines - -Running in a TEE ensures these secrets remain protected, and attestation proves the agent code hasn't been tampered with. - -## Architecture - -``` -┌────────────────────────────────────────────────────────────┐ -│ dstack CVM │ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Your Agent │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ -│ │ │ LangChain│ │ Claude │ │ Custom Agent │ │ │ -│ │ │ Agent │ │ Agent SDK│ │ Framework │ │ │ -│ │ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │ │ -│ │ │ │ │ │ │ -│ │ └─────────────┴─────────────────┘ │ │ -│ │ │ │ │ -│ │ dstack SDK │ │ -│ │ ┌─────────────┴─────────────┐ │ │ -│ │ │ │ │ │ -│ │ get_key() Encrypted Env Vars │ │ -│ │ (wallet keys) (API_KEY, DB_URL) │ │ -│ └───────┴───────────────────────────┴─────────────────┘ │ -│ │ │ -│ /var/run/dstack.sock │ -└────────────────────────────────────────────────────────────┘ -``` - -## Quick Start - -### 1. Docker Compose - -```yaml -# docker-compose.yaml -services: - agent: - image: your-agent-image:latest - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock - environment: - - OPENAI_API_KEY # Encrypted at deploy time - - ANTHROPIC_API_KEY # Encrypted at deploy time - - DATABASE_URL # Encrypted at deploy time - ports: - - "8080:8080" -``` - -### 2. Derive Wallet Keys - -Your agent can derive deterministic keys for blockchain operations: - -```python -from dstack_sdk import DstackClient -from dstack_sdk.ethereum import to_account - -client = DstackClient() - -# Derive Ethereum wallet - same path = same key every time -eth_key = client.get_key('agent/wallet', 'mainnet') -account = to_account(eth_key) - -print(f"Agent wallet: {account.address}") - -# Sign transactions securely -signed_tx = account.sign_transaction(tx_dict) -``` - -### 3. Access Protected Credentials - -API keys are encrypted at deploy time and only decrypted inside the TEE: - -```python -import os -from langchain.llms import OpenAI - -# These were encrypted during deployment -# Only accessible inside this specific TEE -api_key = os.environ["OPENAI_API_KEY"] - -llm = OpenAI(api_key=api_key) -``` - -## LangChain Agent Example - -```python -from langchain.agents import initialize_agent, Tool -from langchain.llms import ChatOpenAI -from dstack_sdk import DstackClient -from dstack_sdk.ethereum import to_account -import os - -client = DstackClient() - -# Derive a wallet for the agent -wallet = to_account(client.get_key('agent/eth-wallet', 'mainnet')) - -def check_balance(address: str) -> str: - """Check ETH balance of an address.""" - # ... web3 code - return f"Balance: {balance} ETH" - -def send_eth(to_address: str, amount: str) -> str: - """Send ETH from agent wallet.""" - # Agent's private key never leaves the TEE - tx = wallet.sign_transaction({ - 'to': to_address, - 'value': int(float(amount) * 1e18), - # ... - }) - return f"Sent {amount} ETH to {to_address}" - -tools = [ - Tool(name="CheckBalance", func=check_balance, description="Check ETH balance"), - Tool(name="SendETH", func=send_eth, description="Send ETH to address"), -] - -# API key is encrypted, only accessible in TEE -llm = ChatOpenAI(api_key=os.environ["OPENAI_API_KEY"]) - -agent = initialize_agent(tools, llm, agent="zero-shot-react-description") -agent.run("Send 0.1 ETH to 0x742d35Cc...") -``` - -## Claude Agent SDK Example - -```python -from anthropic import Anthropic -from dstack_sdk import DstackClient -import os - -client = DstackClient() -anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) - -# Agent with TEE-derived signing keys -signing_key = client.get_key('agent/signing', 'production') - -def execute_with_proof(task: str): - """Execute task and provide attestation proof.""" - - # Run the agent - response = anthropic.messages.create( - model="claude-sonnet-4-20250514", - messages=[{"role": "user", "content": task}] - ) - - result = response.content[0].text - - # Generate attestation proving execution - quote = client.get_quote(result.encode()[:64]) - - return { - "result": result, - "attestation": quote.quote, - "signing_address": signing_key.decode_key().hex()[:40] - } -``` - -## RAG with Protected Documents - -```python -from langchain.vectorstores import Chroma -from langchain.embeddings import OpenAIEmbeddings -from dstack_sdk import DstackClient -import os - -client = DstackClient() - -# Embeddings use protected API key -embeddings = OpenAIEmbeddings(api_key=os.environ["OPENAI_API_KEY"]) - -# Documents stored in TEE-encrypted storage -vectorstore = Chroma( - persist_directory="/data/vectors", - embedding_function=embeddings -) - -def rag_query(question: str) -> dict: - """Query with attestation proof.""" - - docs = vectorstore.similarity_search(question, k=3) - answer = generate_answer(question, docs) - - # Prove the query was executed in TEE - quote = client.get_quote(f"{question}:{answer}".encode()[:64]) - - return { - "answer": answer, - "sources": [d.metadata for d in docs], - "attestation": quote.quote - } -``` - -## Deploying with Encrypted Secrets - -When deploying, encrypt sensitive environment variables: - -```python -from dstack_sdk import encrypt_env_vars, EnvVar -import requests - -# Define secrets -env_vars = [ - EnvVar(key='OPENAI_API_KEY', value='sk-...'), - EnvVar(key='ANTHROPIC_API_KEY', value='sk-ant-...'), - EnvVar(key='DATABASE_URL', value='postgresql://...'), - EnvVar(key='WALLET_MNEMONIC', value='abandon abandon...'), -] - -# Get encryption key from dstack KMS -response = requests.post( - 'https://your-dstack/prpc/GetAppEnvEncryptPubKey?json', - json={'app_id': 'your-app-id'} -) -public_key = response.json()['public_key'] - -# Encrypt - only the TEE can decrypt -encrypted = encrypt_env_vars(env_vars, public_key) - -# Deploy with encrypted secrets -deploy_app(compose_file, encrypted_env=encrypted) -``` - -## Verify Agent Execution - -Clients can verify the agent is running unmodified in a TEE: - -```python -import requests - -# Get attestation from agent -attestation = requests.get( - "https://agent-endpoint/attestation", - params={"nonce": "random-challenge"} -).json() - -# Verify attestation quote via proof.t16z.com or programmatically -is_valid = verify_quote(attestation['quote']) - -# Check agent code hash matches expected -assert attestation['compose_hash'] == EXPECTED_AGENT_HASH -``` - -## Security Guarantees - -| Feature | Protection | -|---------|------------| -| API Keys | Encrypted at deploy, decrypted only in TEE | -| Wallet Keys | Derived deterministically, never leave TEE | -| Agent Code | Measured and attested via TEE | -| Data in Transit | TLS termination inside TEE | -| Execution Proof | Attestation quotes for any operation | - -## Production Considerations - -1. **Key Rotation**: Use versioned paths (`agent/wallet/v2`) for key rotation -2. **Rate Limiting**: Implement rate limits on wallet operations -3. **Audit Logging**: Use `emit_event()` for security-critical actions -4. **Multi-Sig**: Derive multiple keys for threshold signatures - -## Source - -- [dstack Python SDK](../sdk/python/README.md) -- [Environment Encryption Guide](../sdk/python/README.md#environment-variables-encryption) diff --git a/docs/inference.md b/docs/inference.md deleted file mode 100644 index 8933205e..00000000 --- a/docs/inference.md +++ /dev/null @@ -1,112 +0,0 @@ -# Private Inference - -Run LLM inference with hardware attestation and cryptographic proof of responses. - -## Architecture - -``` -┌─────────────┐ ┌──────────────────────────────────────────────┐ -│ Client │ │ dstack CVM │ -│ │ │ ┌─────────────┐ ┌─────────────────┐ │ -│ Request ───┼────►│ │ vllm-proxy │──────│ vLLM Backend │ │ -│ │ │ │ (attestation│ │ (OpenAI API) │ │ -│ ◄──────────┼─────│ │ + signing) │◄─────│ │ │ -│ Response │ │ └─────────────┘ └─────────────────┘ │ -│ + Signature│ │ │ │ -│ + TEE Quote│ │ └── /var/run/dstack.sock │ -└─────────────┘ └──────────────────────────────────────────────┘ -``` - -**vllm-proxy** adds a security layer to vLLM: -- **Hardware attestation** — TEE quotes proving execution in secure hardware -- **Response signing** — Every response cryptographically signed (ECDSA + ED25519) -- **GPU attestation** — NVIDIA Confidential Computing verification (when available) - -## Quick Start - -```yaml -services: - vllm-proxy: - image: dstacktee/vllm-proxy:latest - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock - environment: - - MODEL_NAME=openai/gpt-oss-20b - ports: - - "8000:8000" - - vllm: - image: vllm/vllm-openai:latest - runtime: nvidia - environment: - - NVIDIA_VISIBLE_DEVICES=all - command: --model openai/gpt-oss-20b -``` - -Deploy to [Phala Cloud](https://cloud.phala.network) or any dstack instance. - -## Usage - -Standard OpenAI-compatible API: - -```python -from openai import OpenAI - -client = OpenAI( - base_url="https://your-endpoint:8000/v1", - api_key="your-token" -) - -response = client.chat.completions.create( - model="openai/gpt-oss-20b", - messages=[{"role": "user", "content": "Hello!"}] -) - -print(response.choices[0].message.content) -``` - -## Verify - -Every response can be verified. Retrieve the signature and attestation: - -```python -import requests - -# Get signature for a chat completion -chat_id = response.id -sig = requests.get(f"https://your-endpoint:8000/v1/signature/{chat_id}").json() - -print(f"Request hash: {sig['request_hash']}") -print(f"Response hash: {sig['response_hash']}") -print(f"ECDSA signature: {sig['ecdsa_signature']}") -print(f"Signing address: {sig['signing_address']}") - -# Get attestation report -attestation = requests.get( - f"https://your-endpoint:8000/v1/attestation", - params={"nonce": "your-random-nonce"} -).json() - -print(f"TEE quote: {attestation['quote'][:100]}...") -print(f"GPU evidence: {attestation.get('gpu_evidence', 'N/A')}") -``` - -Or paste the attestation into [proof.t16z.com](https://proof.t16z.com) for visual verification. - -The attestation binds the signing key to the TEE hardware. Verify: -1. TEE quote is valid (via [proof.t16z.com](https://proof.t16z.com) or dstack verifier) -2. Signing address in quote matches the one that signed responses -3. Response hash matches your received content - -## Live Demo - -Try it: [dstack-demo.phala.com](https://dstack-demo.phala.com) - -## Production Users - -- **redpill.ai** — Verifiable AI inference platform -- **NEAR AI** — Decentralized AI services - -## Source - -[github.com/Dstack-TEE/vllm-proxy](https://github.com/Dstack-TEE/vllm-proxy) diff --git a/docs/training.md b/docs/training.md deleted file mode 100644 index 5c9d1ec8..00000000 --- a/docs/training.md +++ /dev/null @@ -1,310 +0,0 @@ -# Confidential Training - -Fine-tune LLMs on sensitive data with hardware-enforced privacy. - -## Why TEE for Training? - -Training data is often the most sensitive asset: -- **Medical records** for healthcare AI -- **Financial data** for fraud detection -- **Proprietary documents** for enterprise RAG -- **User conversations** for personalization - -Running training in a TEE ensures: -- Data is encrypted in memory -- Model weights stay protected -- Training code is attested and verifiable - -## Architecture - -``` -┌──────────────────────────────────────────────────────────────┐ -│ dstack CVM │ -│ │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ Training Pipeline │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ -│ │ │ Encrypted│ │ Unsloth │ │ Fine-tuned │ │ │ -│ │ │ Dataset │───►│ / LoRA │───►│ Model │ │ │ -│ │ │ │ │ Training │ │ (encrypted) │ │ │ -│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ -│ │ ▲ │ │ │ -│ │ │ ▼ │ │ -│ │ Decrypted Model export │ │ -│ │ only in TEE with attestation │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ │ -│ GPU: NVIDIA H100 (Confidential Computing) │ -└──────────────────────────────────────────────────────────────┘ -``` - -## Quick Start - -### Docker Compose - -```yaml -# docker-compose.yaml -services: - trainer: - image: unsloth/unsloth:latest - runtime: nvidia - environment: - - NVIDIA_VISIBLE_DEVICES=all - - HF_TOKEN # Encrypted at deploy time - - WANDB_API_KEY # Encrypted at deploy time - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock - - /mnt/data:/data # Encrypted dataset - - /mnt/models:/models # Model output - command: python train.py -``` - -### Training Script - -```python -# train.py -from unsloth import FastLanguageModel -from dstack_sdk import DstackClient -import os - -client = DstackClient() - -# Verify we're running in TEE -info = client.info() -print(f"Training in TEE: {info.app_id}") -print(f"TCB Info: {info.tcb_info}") - -# Load base model -model, tokenizer = FastLanguageModel.from_pretrained( - model_name="unsloth/Llama-3.2-3B-Instruct", - max_seq_length=2048, - load_in_4bit=True, -) - -# Add LoRA adapters -model = FastLanguageModel.get_peft_model( - model, - r=16, - lora_alpha=16, - lora_dropout=0, - target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], -) - -# Load sensitive training data (decrypted only in TEE) -from datasets import load_dataset -dataset = load_dataset("json", data_files="/data/training.json") - -# Train -from trl import SFTTrainer -from transformers import TrainingArguments - -trainer = SFTTrainer( - model=model, - train_dataset=dataset["train"], - args=TrainingArguments( - output_dir="/models/output", - per_device_train_batch_size=2, - gradient_accumulation_steps=4, - num_train_epochs=3, - learning_rate=2e-4, - fp16=True, - logging_steps=10, - save_strategy="epoch", - ), - tokenizer=tokenizer, -) - -trainer.train() - -# Save with attestation proof -model.save_pretrained("/models/finetuned") - -# Generate attestation for the training run -quote = client.get_quote(b"training-complete") -with open("/models/finetuned/attestation.txt", "w") as f: - f.write(quote.quote) - -print("Training complete with attestation proof") -``` - -## Encrypted Dataset Upload - -Encrypt datasets before uploading: - -```python -from dstack_sdk import encrypt_env_vars, EnvVar -import requests -import json - -# Encrypt the dataset content -dataset = json.dumps([ - {"instruction": "...", "response": "..."}, - # ... sensitive training examples -]) - -env_vars = [EnvVar(key='TRAINING_DATA', value=dataset)] - -# Get encryption key from KMS -response = requests.post( - 'https://your-dstack/prpc/GetAppEnvEncryptPubKey?json', - json={'app_id': 'training-app-id'} -) -public_key = response.json()['public_key'] - -encrypted = encrypt_env_vars(env_vars, public_key) -# Deploy with encrypted dataset -``` - -For large datasets, use encrypted volumes or secure data transfer protocols. - -## Verify Training Provenance - -After training, verify the model was trained in a TEE: - -```python -import requests - -# Get training attestation -with open("model/attestation.txt") as f: - quote = f.read() - -# Verify the quote -# Option 1: Paste into proof.t16z.com for visual verification -# Option 2: Programmatic verification -verification = verify_tdx_quote(quote) - -print(f"Valid TEE: {verification['valid']}") -print(f"App hash: {verification['compose_hash']}") -``` - -## Multi-GPU Training - -For distributed training across multiple GPUs: - -```yaml -# docker-compose.yaml -services: - trainer: - image: unsloth/unsloth:latest - runtime: nvidia - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] - environment: - - NVIDIA_VISIBLE_DEVICES=all - - NCCL_DEBUG=INFO - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock - - /mnt/data:/data - - /mnt/models:/models - command: > - torchrun --nproc_per_node=4 train.py - --model unsloth/Llama-3.2-8B-Instruct - --data /data/training.json -``` - -## Continuous Training Pipeline - -```python -from dstack_sdk import DstackClient -import schedule -import time - -client = DstackClient() - -def training_job(): - """Run scheduled training with attestation.""" - - # Log training start - client.emit_event("training", json.dumps({ - "status": "started", - "timestamp": time.time() - })) - - try: - # Run training - run_training() - - # Generate success attestation - quote = client.get_quote(b"training-success") - upload_model_with_attestation(quote.quote) - - client.emit_event("training", json.dumps({ - "status": "completed", - "timestamp": time.time() - })) - - except Exception as e: - client.emit_event("training", json.dumps({ - "status": "failed", - "error": str(e), - "timestamp": time.time() - })) - raise - -# Schedule daily training -schedule.every().day.at("02:00").do(training_job) - -while True: - schedule.run_pending() - time.sleep(60) -``` - -## NVIDIA Confidential Computing - -For full GPU memory encryption, use NVIDIA H100 with Confidential Computing: - -```yaml -# docker-compose.yaml with CC-enabled GPU -services: - trainer: - image: unsloth/unsloth:latest - runtime: nvidia - environment: - - NVIDIA_VISIBLE_DEVICES=all - - NVIDIA_CC_ACCEPT_EULA=accept # Enable Confidential Computing - volumes: - - /var/run/dstack.sock:/var/run/dstack.sock -``` - -With CC-enabled GPUs: -- GPU memory is encrypted -- Model weights protected during training -- Gradient data stays confidential - -## Security Guarantees - -| Stage | Protection | -|-------|------------| -| Dataset at rest | Encrypted storage | -| Dataset in memory | TEE memory encryption | -| Model weights | TEE + GPU CC memory encryption | -| Gradients | Never leave TEE | -| Output model | Attestation-signed | - -## Frameworks Tested - -| Framework | Status | Notes | -|-----------|--------|-------| -| Unsloth | ✅ Production | Fast LoRA fine-tuning | -| Hugging Face TRL | ✅ Production | Full RLHF support | -| PyTorch | ✅ Production | Native training | -| DeepSpeed | ✅ Tested | Distributed training | -| Axolotl | ✅ Tested | Config-driven training | - -## Production Considerations - -1. **Checkpointing**: Save checkpoints to encrypted storage -2. **Logging**: Use `emit_event()` for audit trails -3. **Data Validation**: Verify dataset integrity before training -4. **Model Export**: Sign models with TEE-derived keys - -## Source - -- [Unsloth](https://github.com/unslothai/unsloth) - Fast LLM fine-tuning -- [dstack Python SDK](../sdk/python/README.md) From 12ee26f03f6d32b0442ca73c687f2d430b5595bb Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Tue, 30 Dec 2025 04:36:01 +0000 Subject: [PATCH 08/16] docs: add GPU TEE deployment guide for agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add .agent/GPU_TEE_DEPLOYMENT.md with learnings from deploying GPU workloads to Phala Cloud: - Instance types (tdx.* for CPU, h200.* for GPU) - Docker Compose GPU configuration - vLLM and vllm-proxy setup - Debugging commands and common issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .agent/GPU_TEE_DEPLOYMENT.md | 235 +++++++++++++++++++++++++++++++++++ .gitignore | 1 + CLAUDE.md | 1 + README.md | 7 ++ 4 files changed, 244 insertions(+) create mode 100644 .agent/GPU_TEE_DEPLOYMENT.md diff --git a/.agent/GPU_TEE_DEPLOYMENT.md b/.agent/GPU_TEE_DEPLOYMENT.md new file mode 100644 index 00000000..ff664eae --- /dev/null +++ b/.agent/GPU_TEE_DEPLOYMENT.md @@ -0,0 +1,235 @@ +# GPU TEE Deployment Guide + +Learnings from deploying GPU workloads to Phala Cloud TEE infrastructure. + +## Instance Types + +Query available instance types: +```bash +curl -s "https://cloud-api.phala.network/api/v1/instance-types" | jq +``` + +### CPU-only (Intel TDX) +- `tdx.small` through `tdx.8xlarge` + +### GPU (H200 + TDX) +- `h200.small` — Single H200 GPU, suitable for inference +- `h200.16xlarge` — Multi-GPU for larger workloads +- `h200.8x.large` — High-memory configuration + +## Deployment Commands + +### GPU Deployment +```bash +phala deploy -n my-app -c docker-compose.yaml \ + --instance-type h200.small \ + --region US-EAST-1 \ + --image dstack-nvidia-dev-0.5.4.1 +``` + +Key flags: +- `--instance-type h200.small` — Required for GPU access +- `--image dstack-nvidia-dev-0.5.4.1` — NVIDIA development image with GPU drivers +- `--region US-EAST-1` — Region with GPU nodes (gpu-use2) + +### Debugging +```bash +# Check CVM status +phala cvms list + +# View serial logs (boot + container output) +phala cvms serial-logs --tail 100 + +# Delete CVM +phala cvms delete --force +``` + +## Docker Compose GPU Configuration + +GPU devices must be explicitly reserved in docker-compose.yaml: + +```yaml +services: + my-gpu-app: + image: my-image + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +Without the `deploy.resources.reservations.devices` section, the container will fail with: +``` +libcuda.so.1: cannot open shared object file: No such file or directory +``` + +## vLLM Example + +Working docker-compose.yaml for vLLM inference: + +```yaml +services: + vllm: + image: vllm/vllm-openai:latest + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock + environment: + - NVIDIA_VISIBLE_DEVICES=all + - HF_TOKEN=${HF_TOKEN:-} + ports: + - "8000:8000" + command: > + --model Qwen/Qwen2.5-1.5B-Instruct + --host 0.0.0.0 + --port 8000 + --max-model-len 4096 + --gpu-memory-utilization 0.8 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] +``` + +## Endpoint URLs + +After deployment, the app is accessible at: +``` +https://-.dstack-pha-.phala.network +``` + +Example for vLLM on port 8000: +```bash +# List models +curl https://-8000.dstack-pha-use2.phala.network/v1/models + +# Chat completion +curl -X POST https://-8000.dstack-pha-use2.phala.network/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model": "Qwen/Qwen2.5-1.5B-Instruct", "messages": [{"role": "user", "content": "Hello"}]}' +``` + +## vllm-proxy (Response Signing) + +vllm-proxy provides response signing and attestation for vLLM inference. It sits between clients and vLLM, signing responses with TEE-derived keys. + +### Configuration + +**IMPORTANT**: The authentication environment variable is `TOKEN`, not `AUTH_TOKEN`. + +```yaml +services: + vllm: + image: vllm/vllm-openai:latest + environment: + - NVIDIA_VISIBLE_DEVICES=all + command: > + --model Qwen/Qwen2.5-1.5B-Instruct + --host 0.0.0.0 + --port 8000 + --max-model-len 4096 + --gpu-memory-utilization 0.8 + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + + proxy: + image: phalanetwork/vllm-proxy:v0.2.18 + volumes: + - /var/run/dstack.sock:/var/run/dstack.sock # Required for TEE key derivation + environment: + - VLLM_BASE_URL=http://vllm:8000 + - MODEL_NAME=Qwen/Qwen2.5-1.5B-Instruct + - TOKEN=your-secret-token # NOT AUTH_TOKEN + ports: + - "8000:8000" + depends_on: + - vllm +``` + +### API Endpoints + +```bash +# List models (no auth required) +curl https:///v1/models + +# Chat completion (requires auth) +curl -X POST https:///v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-secret-token" \ + -d '{"model": "Qwen/Qwen2.5-1.5B-Instruct", "messages": [{"role": "user", "content": "Hello"}]}' + +# Get response signature +curl https:///v1/signature/ \ + -H "Authorization: Bearer your-secret-token" + +# Attestation report +curl https:///v1/attestation/report \ + -H "Authorization: Bearer your-secret-token" +``` + +### Tested Configuration + +- Image: `phalanetwork/vllm-proxy:v0.2.18` +- Instance: `h200.small` +- Region: `US-EAST-1` +- Model: `Qwen/Qwen2.5-1.5B-Instruct` + +### vllm-proxy Issues + +**"Invalid token" error**: +- Check that you're using `TOKEN` environment variable, not `AUTH_TOKEN` +- Verify the token value matches your request header + +**"All connection attempts failed" from proxy**: +- vLLM is still loading the model (takes 1-2 minutes after container starts) +- Wait for vLLM to show "Uvicorn running on" in serial logs + +**NVML error on attestation**: +- GPU confidential computing attestation may not be fully available +- This doesn't affect inference or response signing + +## Common Issues + +### "No available resources match your requirements" +- GPU nodes are limited. Wait for other CVMs to finish or try a different region. +- Ensure you're using the correct instance type (`h200.small`). + +### Container crashes with GPU errors +- Add `deploy.resources.reservations.devices` section to docker-compose.yaml. +- Verify using NVIDIA development image (`dstack-nvidia-dev-*`). + +### Image pull takes too long +- Large images (5GB+ for vLLM) take 3-5 minutes to download and extract. +- Check serial logs for progress. + +## Testing Workflow + +1. Deploy: `phala deploy -n test -c docker-compose.yaml --instance-type h200.small --region US-EAST-1 --image dstack-nvidia-dev-0.5.4.1` +2. Wait for status: `phala cvms list` (wait for "running") +3. Check logs: `phala cvms serial-logs --tail 100` +4. Test API: `curl https://-.dstack-pha-use2.phala.network/...` +5. Cleanup: `phala cvms delete --force` + +## GPU Wrapper Script + +For repeated GPU deployments, use a wrapper script: + +```bash +#!/bin/bash +# phala-gpu.sh +source "$(dirname "$0")/.env" +export PHALA_CLOUD_API_KEY=$PHALA_CLOUD_API_GPU +phala "$@" +``` + +This allows maintaining separate API keys for CPU and GPU workspaces. diff --git a/.gitignore b/.gitignore index c8b7bba0..8c0d716f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ node_modules/ /tmp .claude/settings.local.json __pycache__ +.planning/ diff --git a/CLAUDE.md b/CLAUDE.md index ef533920..2a2f5043 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -229,3 +229,4 @@ When need more detailed info, try to use deepwiki mcp. The `.agent/` directory contains AI assistant resources: - `WRITING_GUIDE.md` — Documentation and README writing guidelines (messaging, style, audiences) +- `GPU_TEE_DEPLOYMENT.md` — GPU deployment to Phala Cloud (instance types, docker-compose config, debugging) diff --git a/README.md b/README.md index fc355b45..80b6a57e 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,13 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use - [Design Decisions](./docs/design-and-hardening-decisions.md) - Architecture rationale - [FAQ](./docs/faq.md) - Frequently asked questions +## Trusted by + +- [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference +- [NEAR AI](https://x.com/ilblackdragon/status/1962920246148268235) - Private AI agents + +dstack is a [Confidential Computing Consortium](https://confidentialcomputing.io/2025/10/02/welcoming-phala-to-the-confidential-computing-consortium/) open source project. + ## Community [Telegram](https://t.me/+UO4bS4jflr45YmUx) · [GitHub Discussions](https://github.com/Dstack-TEE/dstack/discussions) · [Examples](https://github.com/Dstack-TEE/dstack-examples) From 7f8df0e8dbd3020c530d8d458bfb5be59033f99d Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Tue, 30 Dec 2025 08:24:42 +0000 Subject: [PATCH 09/16] docs: consolidate security documentation and add landing page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move security-model.md, security-best-practices.md, and cvm-boundaries.md to docs/security/ subdirectory - Add docs/security/README.md as security landing page with responsible disclosure policy - Add dedicated Security section to main README with links to all security docs - Add base image link to meta-dstack releases in README - Add proof.t16z.com context explaining it's Phala's TEE attestation explorer - Fix broken link in deployment.md (security-guide → security-model) - Promote security audit to top of security-best-practices.md - Rename "Dstack Logo Kit" to "dstack-logo-kit" to avoid URL encoding issues - Remove dangling KNOWN_ISSUES.md reference from Rust SDK README 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 17 +++++++++++------ .../01 Dstack _Horizontal_primary.png | Bin .../01 Dstack _Horizontal_primary.svg | 0 .../02 Dstack _Horizontal_dark.png | Bin .../02 Dstack _Horizontal_dark.svg | 0 .../02 Vertical/01 Dstack_Vertical_primary.png | Bin .../02 Vertical/01 Dstack_Vertical_primary.svg | 0 .../02 Vertical/02 Dstack_Vertical_dark.png | Bin .../02 Vertical/02 Dstack_Vertical_dark.svg | 0 .../03 icon/01 Dstack icon_primary.png | Bin .../03 icon/01 Dstack icon_primary.svg | 0 .../03 icon/02 Dstack icon_dark.png | Bin .../03 icon/02 Dstack icon_dark.svg | 0 docs/confidential-ai.md | 4 ++-- docs/deployment.md | 2 +- docs/onchain-governance.md | 2 +- docs/security/README.md | 17 +++++++++++++++++ docs/{ => security}/cvm-boundaries.md | 2 +- docs/{ => security}/security-best-practices.md | 12 ++++++------ docs/{ => security}/security-model.md | 4 ++-- sdk/rust/README.md | 4 ---- 21 files changed, 41 insertions(+), 23 deletions(-) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/01 Horizontal/01 Dstack _Horizontal_primary.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/01 Horizontal/01 Dstack _Horizontal_primary.svg (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/01 Horizontal/02 Dstack _Horizontal_dark.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/01 Horizontal/02 Dstack _Horizontal_dark.svg (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/02 Vertical/01 Dstack_Vertical_primary.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/02 Vertical/01 Dstack_Vertical_primary.svg (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/02 Vertical/02 Dstack_Vertical_dark.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/02 Vertical/02 Dstack_Vertical_dark.svg (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/03 icon/01 Dstack icon_primary.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/03 icon/01 Dstack icon_primary.svg (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/03 icon/02 Dstack icon_dark.png (100%) rename docs/assets/{Dstack Logo Kit => dstack-logo-kit}/03 icon/02 Dstack icon_dark.svg (100%) create mode 100644 docs/security/README.md rename docs/{ => security}/cvm-boundaries.md (99%) rename docs/{ => security}/security-best-practices.md (93%) rename docs/{ => security}/security-model.md (96%) diff --git a/README.md b/README.md index 80b6a57e..fb59be73 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ services: - "8000:8000" ``` -Deploy to any TDX host with the `dstack-nvidia-0.5.x` base image, or use [Phala Cloud](https://cloud.phala.network) for managed infrastructure. +Deploy to any TDX host with the [`dstack-nvidia-0.5.x` base image](https://github.com/Dstack-TEE/meta-dstack/releases), or use [Phala Cloud](https://cloud.phala.network) for managed infrastructure. Want to deploy a self hosted dstack? Check our [full deployment guide →](./docs/deployment.md) @@ -75,7 +75,7 @@ Your container runs inside a Confidential VM (Intel TDX) with optional GPU isola - **VMM**: Runs on bare-metal TDX hosts. Parses docker-compose files directly — no app changes needed. Boots CVMs from a reproducible OS image. Allocates CPU, memory, and confidential GPU resources. -[Full security model →](./docs/security-model.md) +[Full security model →](./docs/security/security-model.md) ## SDKs @@ -100,14 +100,19 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use - [VMM CLI Guide](./docs/vmm-cli-user-guide.md) - Command-line deployment **Reference** -- [Security Model](./docs/security-model.md) - Threat model and trust boundaries -- [Security Best Practices](./docs/security-best-practices.md) - Production hardening -- [CVM Boundaries](./docs/cvm-boundaries.md) - Information exchange and isolation - [App Compose Format](./docs/normalized-app-compose.md) - Compose file specification - [Gateway](./docs/dstack-gateway.md) - Gateway configuration - [Design Decisions](./docs/design-and-hardening-decisions.md) - Architecture rationale - [FAQ](./docs/faq.md) - Frequently asked questions +## Security + +- [Security Overview](./docs/security/) - Security documentation and responsible disclosure +- [Security Model](./docs/security/security-model.md) - Threat model and trust boundaries +- [Security Best Practices](./docs/security/security-best-practices.md) - Production hardening +- [Security Audit](./docs/security/dstack-audit.pdf) - Third-party audit by zkSecurity +- [CVM Boundaries](./docs/security/cvm-boundaries.md) - Information exchange and isolation + ## Trusted by - [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference @@ -123,7 +128,7 @@ dstack is a [Confidential Computing Consortium](https://confidentialcomputing.io ## Media Kit -The dstack logo and branding assets are available in the [media kit](./docs/assets/Dstack%20Logo%20Kit/): +The dstack logo and branding assets are available in the [media kit](./docs/assets/dstack-logo-kit/): - **Horizontal logos**: Primary and dark versions in PNG/SVG formats - **Vertical logos**: Primary and dark versions in PNG/SVG formats diff --git a/docs/assets/Dstack Logo Kit/01 Horizontal/01 Dstack _Horizontal_primary.png b/docs/assets/dstack-logo-kit/01 Horizontal/01 Dstack _Horizontal_primary.png similarity index 100% rename from docs/assets/Dstack Logo Kit/01 Horizontal/01 Dstack _Horizontal_primary.png rename to docs/assets/dstack-logo-kit/01 Horizontal/01 Dstack _Horizontal_primary.png diff --git a/docs/assets/Dstack Logo Kit/01 Horizontal/01 Dstack _Horizontal_primary.svg b/docs/assets/dstack-logo-kit/01 Horizontal/01 Dstack _Horizontal_primary.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/01 Horizontal/01 Dstack _Horizontal_primary.svg rename to docs/assets/dstack-logo-kit/01 Horizontal/01 Dstack _Horizontal_primary.svg diff --git a/docs/assets/Dstack Logo Kit/01 Horizontal/02 Dstack _Horizontal_dark.png b/docs/assets/dstack-logo-kit/01 Horizontal/02 Dstack _Horizontal_dark.png similarity index 100% rename from docs/assets/Dstack Logo Kit/01 Horizontal/02 Dstack _Horizontal_dark.png rename to docs/assets/dstack-logo-kit/01 Horizontal/02 Dstack _Horizontal_dark.png diff --git a/docs/assets/Dstack Logo Kit/01 Horizontal/02 Dstack _Horizontal_dark.svg b/docs/assets/dstack-logo-kit/01 Horizontal/02 Dstack _Horizontal_dark.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/01 Horizontal/02 Dstack _Horizontal_dark.svg rename to docs/assets/dstack-logo-kit/01 Horizontal/02 Dstack _Horizontal_dark.svg diff --git a/docs/assets/Dstack Logo Kit/02 Vertical/01 Dstack_Vertical_primary.png b/docs/assets/dstack-logo-kit/02 Vertical/01 Dstack_Vertical_primary.png similarity index 100% rename from docs/assets/Dstack Logo Kit/02 Vertical/01 Dstack_Vertical_primary.png rename to docs/assets/dstack-logo-kit/02 Vertical/01 Dstack_Vertical_primary.png diff --git a/docs/assets/Dstack Logo Kit/02 Vertical/01 Dstack_Vertical_primary.svg b/docs/assets/dstack-logo-kit/02 Vertical/01 Dstack_Vertical_primary.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/02 Vertical/01 Dstack_Vertical_primary.svg rename to docs/assets/dstack-logo-kit/02 Vertical/01 Dstack_Vertical_primary.svg diff --git a/docs/assets/Dstack Logo Kit/02 Vertical/02 Dstack_Vertical_dark.png b/docs/assets/dstack-logo-kit/02 Vertical/02 Dstack_Vertical_dark.png similarity index 100% rename from docs/assets/Dstack Logo Kit/02 Vertical/02 Dstack_Vertical_dark.png rename to docs/assets/dstack-logo-kit/02 Vertical/02 Dstack_Vertical_dark.png diff --git a/docs/assets/Dstack Logo Kit/02 Vertical/02 Dstack_Vertical_dark.svg b/docs/assets/dstack-logo-kit/02 Vertical/02 Dstack_Vertical_dark.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/02 Vertical/02 Dstack_Vertical_dark.svg rename to docs/assets/dstack-logo-kit/02 Vertical/02 Dstack_Vertical_dark.svg diff --git a/docs/assets/Dstack Logo Kit/03 icon/01 Dstack icon_primary.png b/docs/assets/dstack-logo-kit/03 icon/01 Dstack icon_primary.png similarity index 100% rename from docs/assets/Dstack Logo Kit/03 icon/01 Dstack icon_primary.png rename to docs/assets/dstack-logo-kit/03 icon/01 Dstack icon_primary.png diff --git a/docs/assets/Dstack Logo Kit/03 icon/01 Dstack icon_primary.svg b/docs/assets/dstack-logo-kit/03 icon/01 Dstack icon_primary.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/03 icon/01 Dstack icon_primary.svg rename to docs/assets/dstack-logo-kit/03 icon/01 Dstack icon_primary.svg diff --git a/docs/assets/Dstack Logo Kit/03 icon/02 Dstack icon_dark.png b/docs/assets/dstack-logo-kit/03 icon/02 Dstack icon_dark.png similarity index 100% rename from docs/assets/Dstack Logo Kit/03 icon/02 Dstack icon_dark.png rename to docs/assets/dstack-logo-kit/03 icon/02 Dstack icon_dark.png diff --git a/docs/assets/Dstack Logo Kit/03 icon/02 Dstack icon_dark.svg b/docs/assets/dstack-logo-kit/03 icon/02 Dstack icon_dark.svg similarity index 100% rename from docs/assets/Dstack Logo Kit/03 icon/02 Dstack icon_dark.svg rename to docs/assets/dstack-logo-kit/03 icon/02 Dstack icon_dark.svg diff --git a/docs/confidential-ai.md b/docs/confidential-ai.md index 5d2ad350..724a53b9 100644 --- a/docs/confidential-ai.md +++ b/docs/confidential-ai.md @@ -230,7 +230,7 @@ expected_hash = "sha256:abc123..." # From your audited docker-compose assert attestation["compose_hash"] == expected_hash ``` -For visual verification, paste the quote into [proof.t16z.com](https://proof.t16z.com). +For visual verification, paste the quote into [proof.t16z.com](https://proof.t16z.com), Phala's TEE attestation explorer that parses TDX quotes and displays the verification status, measurements, and TCB info in a readable format. The compose hash only tells you *what* is running. You still need to verify that code does what you expect—no secret logging, no data exfiltration, proper access controls. @@ -244,7 +244,7 @@ See the [full benchmark report](https://docs.phala.network/dstack/phala-cloud/re 1. Try [chat.redpill.ai](https://chat.redpill.ai) to see private inference in action 2. Deploy your own with the [inference example](https://github.com/Dstack-TEE/dstack-examples/tree/main/ai/inference) -3. Read the [Security Model](./security-model.md) for the full threat model +3. Read the [Security Model](./security/security-model.md) for the full threat model ## Production diff --git a/docs/deployment.md b/docs/deployment.md index cfed5731..463e1aeb 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -159,7 +159,7 @@ port = 10000 Download guest images from [meta-dstack releases](https://github.com/Dstack-TEE/meta-dstack/releases) and extract to `./images/`. -> For reproducible builds and verification, see [Security Guide](./security-guide/security-guide.md). +> For reproducible builds and verification, see the [Security Model](./security/security-model.md). Start VMM: diff --git a/docs/onchain-governance.md b/docs/onchain-governance.md index d76fb113..67497f8b 100644 --- a/docs/onchain-governance.md +++ b/docs/onchain-governance.md @@ -186,4 +186,4 @@ Source: [`kms/auth-eth/contracts/`](../kms/auth-eth/contracts/) ## See Also - [Deployment Guide](./deployment.md) - Setting up dstack infrastructure -- [Security Best Practices](./security-best-practices.md) +- [Security Best Practices](./security/security-best-practices.md) diff --git a/docs/security/README.md b/docs/security/README.md new file mode 100644 index 00000000..bc82eded --- /dev/null +++ b/docs/security/README.md @@ -0,0 +1,17 @@ +# Security Documentation + +dstack security resources for auditors, researchers, and operators. + +## Audit + +dstack has been audited by zkSecurity. See the [full audit report](./dstack-audit.pdf). + +## Documentation + +- [Security Model](./security-model.md) - Threat model, trust boundaries, and verification checklist +- [Security Best Practices](./security-best-practices.md) - Production hardening guide +- [CVM Boundaries](./cvm-boundaries.md) - Information exchange and isolation details + +## Responsible Disclosure + +To report a security vulnerability, email security@phala.network. We will respond within 48 hours. diff --git a/docs/cvm-boundaries.md b/docs/security/cvm-boundaries.md similarity index 99% rename from docs/cvm-boundaries.md rename to docs/security/cvm-boundaries.md index 975fd278..06b8e0cb 100644 --- a/docs/cvm-boundaries.md +++ b/docs/security/cvm-boundaries.md @@ -117,7 +117,7 @@ dstack uses encrypted environment variables to allow app developers to securely - CVM performs basic regex validation on values - Final result is stored as /dstack/.hostshared/.decrypted-env and loaded system-wide via app-compose.service -This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security-best-practices.md](security-best-practices.md) for more details. +This file is not measured to RTMRs. But it is highly recommended to add application-specific integrity checks on encrypted environment variables at the application layer. See [security-best-practices.md](./security-best-practices.md) for more details. ### .user-config This is an optional application-specific configuration file that applications inside the CVM can access. dstack OS simply stores it at /dstack/user-config without any measurement or additional processing. diff --git a/docs/security-best-practices.md b/docs/security/security-best-practices.md similarity index 93% rename from docs/security-best-practices.md rename to docs/security/security-best-practices.md index 6bfed541..e93ffb68 100644 --- a/docs/security-best-practices.md +++ b/docs/security/security-best-practices.md @@ -2,6 +2,10 @@ This document describes security considerations for deploying dstack apps in production. +## Security Audit + +dstack has been audited by [zkSecurity](https://www.zksecurity.xyz/). The audit covered the KMS, guest agent, and attestation verification components. See the [full audit report](./dstack-audit.pdf) for findings and remediation status. + ## Always pin image hash in your docker-compose.yaml When deploying applications in a TEE environment, it's critical to ensure the integrity and immutability of your container images. Using image digests (SHA256 hashes) instead of tags cryptographically ensures that the exact same image is always pulled, preventing supply chain attacks. This proves to users that your App is anchored to a specific code version. @@ -38,11 +42,11 @@ dstack provides encrypted environment variable functionality. Although the CVM p If you use dstack-vmm's built-in UI, the prelaunch script has already been automatically filled in for you: -![Prelaunch Script](./assets/prelaunch-script.png) +![Prelaunch Script](../assets/prelaunch-script.png) You only need to add the `APP_LAUNCH_TOKEN` environment variable to enable LAUNCH_TOKEN checking. -![Token Environment Variable](./assets/token-env.png) +![Token Environment Variable](../assets/token-env.png) user_config is not encrypted, and similarly requires integrity checks at the application layer. For example, you can store a USER_CONFIG_HASH in encrypted environment variables and verify it in the prelaunch script. @@ -119,7 +123,3 @@ services: image: nginx@sha256:eee5eae48e79b2e75178328c7c585b89d676eaae616f03f9a1813aaed820745a network_mode: host ``` - -## Security Audit - -dstack has undergone a security audit. See the [full audit report](./security/dstack-audit.pdf). diff --git a/docs/security-model.md b/docs/security/security-model.md similarity index 96% rename from docs/security-model.md rename to docs/security/security-model.md index 93a8e955..b07e6c05 100644 --- a/docs/security-model.md +++ b/docs/security/security-model.md @@ -120,7 +120,7 @@ Attestation proves which code is running, not that the code is bug-free. It prov ### Environment variables need application-layer authentication -Encrypted environment variables prevent the host from reading your secrets. However, the host can replace encrypted values with different ones. Your application should verify authenticity using patterns like LAUNCH_TOKEN. See [security-best-practices.md](security-best-practices.md) for details. +Encrypted environment variables prevent the host from reading your secrets. However, the host can replace encrypted values with different ones. Your application should verify authenticity using patterns like LAUNCH_TOKEN. See [security-best-practices.md](./security-best-practices.md) for details. ### KMS root key security @@ -128,4 +128,4 @@ All keys derive from the KMS root key, which is protected by TEE isolation. Like ## Further Reading -For production deployment guidance, see [security-best-practices.md](security-best-practices.md). For smart contract authorization details, see [onchain-governance.md](onchain-governance.md). For technical details about CVM boundaries and APIs, see [cvm-boundaries.md](cvm-boundaries.md). +For production deployment guidance, see [security-best-practices.md](./security-best-practices.md). For smart contract authorization details, see [onchain-governance.md](../onchain-governance.md). For technical details about CVM boundaries and APIs, see [cvm-boundaries.md](./cvm-boundaries.md). diff --git a/sdk/rust/README.md b/sdk/rust/README.md index a41e2cd2..a6b0056a 100644 --- a/sdk/rust/README.md +++ b/sdk/rust/README.md @@ -234,10 +234,6 @@ Method changes: - `derive_key()` → `get_tls_key()` for TLS certificates - Socket path: `/var/run/tappd.sock` → `/var/run/dstack.sock` -## Known Issues - -See [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for current limitations and workarounds. - ## License Apache License 2.0 From 86828567aeb7f6ec6edeb2fb377d0b932b37b7ac Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 01:18:14 +0000 Subject: [PATCH 10/16] docs: add 'What You Can Build' section to confidential AI guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add developer-focused framing that highlights what can be built with confidential AI without being pitchy. Covers private inference, training on sensitive data, and trustworthy agents. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/confidential-ai.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/confidential-ai.md b/docs/confidential-ai.md index 724a53b9..8d4e2f8a 100644 --- a/docs/confidential-ai.md +++ b/docs/confidential-ai.md @@ -2,6 +2,14 @@ Run AI workloads where the infrastructure operator can't see your data. dstack uses Intel TDX and NVIDIA Confidential Computing to encrypt everything in memory—your prompts, model weights, and intermediate computations stay private. +## What You Can Build + +- **Private inference** - Users verify their prompts never leave encrypted memory +- **Training on sensitive data** - Fine-tune models without exposing training data to operators +- **Trustworthy agents** - Prove your agent code can't exfiltrate user data + +The key difference from self-hosting: users don't have to trust you. They can cryptographically verify what code runs and that the hardware is genuine. + ## What Makes It Confidential Four things need to be true for the system to be actually confidential: From 852ef31e1a4380595f970df47cf5b13750d0ff4e Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 01:50:18 +0000 Subject: [PATCH 11/16] Update "trusted by" section --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb59be73..efe19ee1 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,10 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use ## Trusted by -- [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference -- [NEAR AI](https://x.com/ilblackdragon/status/1962920246148268235) - Private AI agents +- [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference providers are powered by dstack +- [NEAR AI](https://x.com/ilblackdragon/status/1962920246148268235) - Private AI infrastructure powered by dstack -dstack is a [Confidential Computing Consortium](https://confidentialcomputing.io/2025/10/02/welcoming-phala-to-the-confidential-computing-consortium/) open source project. +dstack is a Linux Foundation [Confidential Computing Consortium](https://confidentialcomputing.io/2025/10/02/welcoming-phala-to-the-confidential-computing-consortium/) open source project. ## Community From 1ee2284ae536b311f3f38488cba7692c426e3a59 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 02:01:56 +0000 Subject: [PATCH 12/16] docs: add verification tutorial and reorganize documentation index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add practical verification guide covering: - Visual verification with proof.t16z.com - Programmatic verification via guest agent API - Links to comprehensive docs at docs.phala.network Reorganize README documentation section by audience: - For Developers: building apps on dstack - For Operators: running dstack infrastructure - Reference: specifications and CLI reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 13 +++++++------ docs/verification.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 docs/verification.md diff --git a/README.md b/README.md index efe19ee1..33a9d021 100644 --- a/README.md +++ b/README.md @@ -90,18 +90,19 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use ## Documentation -**Use Cases** +**For Developers** - [Confidential AI](./docs/confidential-ai.md) - Inference, agents, and training with hardware privacy - -**Guides** - [Usage Guide](./docs/usage.md) - Deploying and managing apps +- [Verification](./docs/verification.md) - How to verify TEE attestation + +**For Operators** - [Deployment](./docs/deployment.md) - Self-hosting on TDX hardware -- [On-Chain Governance](./docs/onchain-governance.md) - Smart contract-based authorization -- [VMM CLI Guide](./docs/vmm-cli-user-guide.md) - Command-line deployment +- [On-Chain Governance](./docs/onchain-governance.md) - Smart contract authorization +- [Gateway](./docs/dstack-gateway.md) - Gateway configuration **Reference** - [App Compose Format](./docs/normalized-app-compose.md) - Compose file specification -- [Gateway](./docs/dstack-gateway.md) - Gateway configuration +- [VMM CLI Guide](./docs/vmm-cli-user-guide.md) - Command-line reference - [Design Decisions](./docs/design-and-hardening-decisions.md) - Architecture rationale - [FAQ](./docs/faq.md) - Frequently asked questions diff --git a/docs/verification.md b/docs/verification.md new file mode 100644 index 00000000..17c5170c --- /dev/null +++ b/docs/verification.md @@ -0,0 +1,33 @@ +# Verification + +Attestation is cryptographic proof that your app runs in genuine TEE hardware with exactly the code you expect. No one can fake it. + +## What Attestation Proves + +When you verify a dstack deployment, you're checking three things: + +1. **Genuine hardware** - Hardware vendor signatures confirm real TEE hardware generated the proof (Intel TDX, NVIDIA CC, or AMD SEV) +2. **Correct code** - The compose-hash matches your docker-compose configuration +3. **Secure environment** - OS and firmware measurements show no tampering + +If any of these fail, the cryptographic proof won't verify. + +## How to Verify + +**Phala Cloud users**: Every deployment gets an automatic [Trust Center](https://trust.phala.com) report. This verifies hardware, code, and environment without manual steps. + +**Programmatic verification**: dstack provides several tools: + +- [dstack-verifier](https://github.com/Dstack-TEE/dstack/tree/master/verifier) - HTTP service with `/verify` endpoint, also runs as CLI +- [dcap-qvl](https://github.com/Phala-Network/dcap-qvl) - Open source quote verification library (Rust, Python, JS/WASM, CLI) +- [SDKs](../sdk/) - JavaScript and Python SDKs include `replayRtmrs()` for local RTMR verification + +## Learn More + +- [Attestation Documentation](https://docs.phala.com/phala-cloud/attestation/overview) - Generating quotes, programmatic verification, RTMR3 replay +- [Confidential AI Verification](https://docs.phala.com/phala-cloud/confidential-ai/verify/overview) - GPU TEE attestation for AI workloads +- [Domain Attestation](https://docs.phala.com/phala-cloud/networking/domain-attestation) - TLS certificates managed in TEE + +## See It Live + +Visit [chat.redpill.ai](https://chat.redpill.ai) and click the shield icon next to any response. This shows attestation verification from a real confidential AI deployment. From 1453564a629d2ff2e6b5608b619ae6343af5b770 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 02:33:29 +0000 Subject: [PATCH 13/16] docs: add FAQ section with comparison and collapsible content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add FAQ section covering: - Why not use cloud provider TEEs directly (with comparison table) - Difference from SGX/Gramine - Performance overhead - Production readiness - Hardware support - Verification process Consolidate media kit to single line. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 33a9d021..8b876c6d 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,69 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use - [Security Audit](./docs/security/dstack-audit.pdf) - Third-party audit by zkSecurity - [CVM Boundaries](./docs/security/cvm-boundaries.md) - Information exchange and isolation +## FAQ + +
+Why not use AWS Nitro / Azure Confidential VMs / GCP directly? + +You can — but you'll build everything yourself: attestation verification, key management, Docker orchestration, certificate provisioning, and governance. dstack provides all of this out of the box. + +| Approach | Docker native | GPU TEE | Key management | Attestation tooling | Open source | +|----------|:-------------:|:-------:|:--------------:|:-------------------:|:-----------:| +| **dstack** | ✓ | ✓ | ✓ | ✓ | ✓ | +| AWS Nitro Enclaves | - | - | Manual | Manual | - | +| Azure Confidential VMs | - | Preview | Manual | Manual | - | +| GCP Confidential Computing | - | - | Manual | Manual | - | + +Cloud providers give you the hardware primitive. dstack gives you the full stack: reproducible OS images, automatic attestation, per-app key derivation, TLS certificates, and smart contract governance. No vendor lock-in. + +
+ +
+How is this different from SGX/Gramine? + +SGX requires porting applications to enclaves. dstack uses full-VM isolation (Intel TDX) — bring your Docker containers as-is. Plus GPU TEE support that SGX doesn't offer. + +
+ +
+What's the performance overhead? + +Minimal. Intel TDX adds ~2-5% overhead for CPU workloads. NVIDIA Confidential Computing has negligible impact on GPU inference. The main cost is memory encryption, which is hardware-accelerated on supported CPUs. + +
+ +
+Is this production-ready? + +Yes. dstack powers production AI infrastructure at [OpenRouter](https://openrouter.ai/provider/phala) and [NEAR AI](https://x.com/ilblackdragon/status/1962920246148268235). The framework has been [audited by zkSecurity](./docs/security/dstack-audit.pdf) and is a Linux Foundation Confidential Computing Consortium project. + +
+ +
+Can I run this on my own hardware? + +Yes. dstack runs on any Intel TDX-capable server. See the [deployment guide](./docs/deployment.md) for self-hosting instructions. You can also use [Phala Cloud](https://cloud.phala.network) for managed infrastructure. + +
+ +
+What TEE hardware is supported? + +Currently: Intel TDX (4th/5th Gen Xeon) and NVIDIA Confidential Computing (H100, Blackwell). AMD SEV-SNP support is planned. + +
+ +
+How do users verify my deployment? + +Your app exposes attestation quotes via the SDK. Users verify these quotes using [dstack-verifier](https://github.com/Dstack-TEE/dstack/tree/master/verifier), [dcap-qvl](https://github.com/Phala-Network/dcap-qvl), or the [Trust Center](https://trust.phala.com). See the [verification guide](./docs/verification.md) for details. + +
+ ## Trusted by -- [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference providers are powered by dstack +- [OpenRouter](https://openrouter.ai/provider/phala) - Confidential AI inference providers powered by dstack - [NEAR AI](https://x.com/ilblackdragon/status/1962920246148268235) - Private AI infrastructure powered by dstack dstack is a Linux Foundation [Confidential Computing Consortium](https://confidentialcomputing.io/2025/10/02/welcoming-phala-to-the-confidential-computing-consortium/) open source project. @@ -129,11 +189,7 @@ dstack is a Linux Foundation [Confidential Computing Consortium](https://confide ## Media Kit -The dstack logo and branding assets are available in the [media kit](./docs/assets/dstack-logo-kit/): - -- **Horizontal logos**: Primary and dark versions in PNG/SVG formats -- **Vertical logos**: Primary and dark versions in PNG/SVG formats -- **Icons**: Primary and dark versions in PNG/SVG formats +Logo and branding assets: [dstack-logo-kit](./docs/assets/dstack-logo-kit/) ## License From c3b6acbc577d85626127f4c8921f4cdf908a7260 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 03:05:34 +0000 Subject: [PATCH 14/16] docs: add citation section with arXiv reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add BibTeX citation for the dstack paper: "Dstack: A Zero Trust Framework for Confidential Containers" (arXiv:2509.11555) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 8b876c6d..2ed0d551 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,19 @@ dstack is a Linux Foundation [Confidential Computing Consortium](https://confide [![Repobeats](https://repobeats.axiom.co/api/embed/0a001cc3c1f387fae08172a9e116b0ec367b8971.svg)](https://github.com/Dstack-TEE/dstack/pulse) +## Cite + +If you use dstack in your research, please cite: + +```bibtex +@article{zhou2025dstack, + title={Dstack: A Zero Trust Framework for Confidential Containers}, + author={Zhou, Shunfan and Wang, Kevin and Yin, Hang}, + journal={arXiv preprint arXiv:2509.11555}, + year={2025} +} +``` + ## Media Kit Logo and branding assets: [dstack-logo-kit](./docs/assets/dstack-logo-kit/) From 842517d4b82e5d94a288f98d4e72d140730da780 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 08:50:42 +0000 Subject: [PATCH 15/16] chore: fix reuse lint errors for dependency files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add precedence override for openzeppelin-foundry-upgrades and forge-std to handle invalid SPDX expressions in vendored dependencies - Add missing annotations for kms/auth-simple config files - Add override for scripts/add-spdx-attribution.py to prevent false positive SPDX detection from script content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- REUSE.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/REUSE.toml b/REUSE.toml index 8f345c27..1b6dff22 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -27,6 +27,8 @@ path = [ "**/package-lock.json", "kms/auth-eth/.openzeppelin/unknown-2035.json", "kms/auth-mock/.oxlintrc.json", + "kms/auth-simple/.oxlintrc.json", + "kms/auth-simple/auth-config.example.json", "sdk/simulator/*.json", "sdk/go/go.sum", "kms/dstack-app/builder/shared/kms-pinned-packages.txt", @@ -84,6 +86,14 @@ path = [ SPDX-FileCopyrightText = "Copyright (c) 2024-2025 The Project Contributors" SPDX-License-Identifier = "Apache-2.0" +# Scripts with SPDX-like content (false positive prevention) + +[[annotations]] +path = "scripts/add-spdx-attribution.py" +SPDX-FileCopyrightText = "© 2025 Phala Network " +SPDX-License-Identifier = "Apache-2.0" +precedence = "override" + # Vendor code [[annotations]] @@ -96,11 +106,13 @@ precedence = "override" path = "kms/auth-eth/lib/openzeppelin-foundry-upgrades/**" SPDX-FileCopyrightText = "NONE" SPDX-License-Identifier = "MIT" +precedence = "override" [[annotations]] path = "kms/auth-eth/lib/forge-std/**" SPDX-FileCopyrightText = "NONE" SPDX-License-Identifier = "Apache-2.0" +precedence = "override" [[annotations]] path = "tdx-attest-sys/csrc/*" From 5ddf1e98afe3b3287004ef20a806e17ad2c88688 Mon Sep 17 00:00:00 2001 From: Hang Yin Date: Wed, 31 Dec 2025 11:36:51 +0000 Subject: [PATCH 16/16] fix(ct_monitor): enable TLS certificate verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove dangerous `danger_accept_invalid_certs(true)` - gateway uses valid certs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- ct_monitor/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ct_monitor/src/main.rs b/ct_monitor/src/main.rs index 8f0f268d..4622e9f4 100644 --- a/ct_monitor/src/main.rs +++ b/ct_monitor/src/main.rs @@ -56,10 +56,7 @@ impl Monitor { let acme_info_url = format!("{}/acme-info", self.gateway_uri.trim_end_matches('/')); info!("fetching known public keys from {}", acme_info_url); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) // TODO: Use RA-TLS verification - .build() - .context("failed to build http client")?; + let client = reqwest::Client::new(); let response = client .get(&acme_info_url)