Skip to content

Commit d6d12e7

Browse files
authored
Merge pull request #803 from Dstack-TEE/docs/no-tee-swtpm-development
docs: add no-TEE swtpm development guide
2 parents f0d0e06 + b49df0e commit d6d12e7

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ Setting up dstack on your own hardware? Start with the [self-hosted quick onboar
7373

7474
Building or customizing the guest OS itself? Follow the [guest-OS build guide](./docs/building-guest-os.md).
7575

76+
Developing without TEE hardware? Use a development image with
77+
[no-TEE mode and swtpm](./docs/development-without-tee.md).
78+
7679
## Architecture
7780

7881
![Architecture](./docs/assets/arch.png)

docs/development-without-tee.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Develop with dstack without TEE hardware
2+
3+
Development guest images can run dstack's normal guest setup on a KVM machine
4+
that has no TDX or SEV-SNP support. The VMM starts the guest with `no_tee`, the
5+
guest supplies the TDX ABI through `dstack-tee-simulator`, and `swtpm` provides
6+
persistent TPM-backed application keys. This is suitable for development and
7+
integration testing, not for production workloads or secrets.
8+
9+
## What this mode tests
10+
11+
The development simulator provides configfs-tsm reports, RTMR extension files,
12+
and a CCEL event log. Guest preparation, measurements, application key setup,
13+
encrypted persistent storage, the guest agent, and Docker Compose therefore
14+
use their normal code paths.
15+
16+
It does not provide hardware isolation or a valid hardware-signed quote. The
17+
host controls QEMU and swtpm, and a production verifier or KMS must reject the
18+
simulated quote.
19+
20+
## Build the development image on the host
21+
22+
TEE hardware is not needed for the build. Follow the prerequisites in
23+
[Build the dstack guest OS](building-guest-os.md), then run this from the
24+
repository root:
25+
26+
```bash
27+
make os-deps
28+
cd os/yocto/repro-build
29+
RELEASE_FLAVORS="dev" ./repro-build.sh -n
30+
```
31+
32+
The artifact used below is
33+
`os/yocto/repro-build/dist/dstack-dev-<version>.tar.gz`. Confirm that it really
34+
is a development image:
35+
36+
```bash
37+
mkdir -p ~/.dstack-vmm/image
38+
tar -xzf os/yocto/repro-build/dist/dstack-dev-*.tar.gz \
39+
-C ~/.dstack-vmm/image
40+
jq '{version, git_revision, is_dev}' \
41+
~/.dstack-vmm/image/dstack-dev-*/metadata.json
42+
```
43+
44+
Expected output includes `"is_dev": true`. Use a development image for this
45+
workflow because production images do not include `dstack-tee-simulator`.
46+
47+
## Install and configure the VMM
48+
49+
Install QEMU and swtpm on the development host. On Ubuntu:
50+
51+
```bash
52+
sudo apt-get update
53+
sudo apt-get install -y qemu-system-x86 swtpm swtpm-tools jq
54+
test -r /dev/kvm && test -w /dev/kvm
55+
```
56+
57+
Add the current user to the `kvm` group and log in again if the `/dev/kvm`
58+
check fails because of its permissions.
59+
60+
Build the VMM and supervisor from the same checkout as the image:
61+
62+
```bash
63+
cargo build --manifest-path dstack/Cargo.toml --release \
64+
-p dstack-vmm -p supervisor
65+
```
66+
67+
Install both binaries and the CLI on the machine that will run QEMU. You can
68+
also run them directly from the checkout during development:
69+
70+
```bash
71+
mkdir -p ~/.dstack-vmm
72+
install -Dm755 dstack/target/release/dstack-vmm ~/.local/bin/dstack-vmm
73+
install -Dm755 dstack/target/release/supervisor ~/.local/bin/supervisor
74+
install -Dm755 dstack/vmm/src/vmm-cli.py ~/.local/bin/dstack
75+
mkdir -p ~/.dstack-vmm
76+
cp dstack/vmm/vmm.toml ~/.dstack-vmm/vmm.toml
77+
```
78+
79+
Set these values in `~/.dstack-vmm/vmm.toml`:
80+
81+
```toml
82+
[image]
83+
path = "/home/USER/.dstack-vmm/image"
84+
85+
[cvm]
86+
qemu_path = "/usr/bin/qemu-system-x86_64"
87+
88+
[cvm.networking]
89+
mode = "user"
90+
91+
[supervisor]
92+
exe = "/home/USER/.local/bin/supervisor"
93+
```
94+
95+
Start the VMM from a stable working directory because its default API socket is
96+
relative to that directory:
97+
98+
```bash
99+
mkdir -p ~/.dstack-vmm/run
100+
cd ~/.dstack-vmm/run
101+
dstack-vmm -c ../vmm.toml
102+
```
103+
104+
In another terminal, check that the development image is visible:
105+
106+
```bash
107+
cd ~/.dstack-vmm/run
108+
dstack lsimage --json | jq '.[] | {name, version, is_dev}'
109+
```
110+
111+
## Launch with no TEE and swtpm
112+
113+
Create a small stateful workload:
114+
115+
```yaml
116+
# docker-compose.yml
117+
services:
118+
state-test:
119+
image: alpine:3.20
120+
command:
121+
- sh
122+
- -c
123+
- |
124+
date -Iseconds >> /data/boots
125+
touch /data/persistent-marker
126+
sleep infinity
127+
volumes:
128+
- state-data:/data
129+
volumes:
130+
state-data: {}
131+
```
132+
133+
Convert it to app-compose JSON and select the TPM key provider:
134+
135+
```bash
136+
dstack compose \
137+
--name swtpm-persistence \
138+
--docker-compose docker-compose.yml \
139+
--key-provider tpm \
140+
--public-logs \
141+
--public-sysinfo \
142+
--output app-compose.json
143+
```
144+
145+
Deploy with the development image, no KMS, and no TEE:
146+
147+
```bash
148+
dstack deploy \
149+
--name swtpm-persistence \
150+
--image dstack-dev-<version> \
151+
--compose app-compose.json \
152+
--vcpu 2 --memory 3G --disk 10G \
153+
--no-tee
154+
```
155+
156+
Successful output contains a VM ID. `dstack info VM_ID` should eventually show
157+
`Boot Progress: done`. The QEMU command line should contain the swtpm frontend
158+
and no TDX guest object:
159+
160+
```bash
161+
ps aux | grep '[q]emu-system' | grep -E -- '-tpmdev|tpm-tis'
162+
ps aux | grep '[s]wtpm socket'
163+
```
164+
165+
The VMM stores both durable components below the VM work directory:
166+
167+
```text
168+
~/.dstack-vmm/vm/VM_ID/hda.img
169+
~/.dstack-vmm/vm/VM_ID/swtpm/tpm2-00.permall
170+
```
171+
172+
Do not delete either path if the VM must restart with the same state.
173+
174+
## Verify encrypted storage and restart persistence
175+
176+
First check the guest preparation log:
177+
178+
```bash
179+
dstack logs VM_ID -n 300 | grep -E \
180+
'Generating app keys from TPM|Filesystem options|LUKS2 header|Device mapper'
181+
```
182+
183+
A successful first boot includes output like:
184+
185+
```text
186+
Generating app keys from TPM
187+
Filesystem options: encryption=true, filesystem=Zfs
188+
Mounting tmpfs for in-memory LUKS header
189+
Loading the LUKS2 header
190+
Device mapper /dev/mapper/dstack_data_disk is ready
191+
```
192+
193+
Record the instance ID, then perform a graceful restart:
194+
195+
```bash
196+
dstack info VM_ID | grep 'Instance ID'
197+
dstack stop VM_ID
198+
dstack start VM_ID
199+
```
200+
201+
Wait for `Boot Progress: done`, then inspect the recent events and boot log:
202+
203+
```bash
204+
dstack info VM_ID
205+
dstack logs VM_ID -n 300 | grep -E \
206+
'mounting data disk|Loading the LUKS2 header|Device mapper|Container .* Started'
207+
```
208+
209+
The restart passes when all of the following hold:
210+
211+
1. the instance ID is unchanged;
212+
2. the event stream says `mounting data disk`, not `initializing data disk`;
213+
3. the LUKS mapping becomes ready without reformatting the disk;
214+
4. Docker starts the existing container and volume, and
215+
`/data/persistent-marker` and the earlier entries in `/data/boots` remain.
216+
217+
## Common failures
218+
219+
`tpm key provider requested but swtpm is not installed` means `swtpm` is not on
220+
the VMM user's `PATH`. If QEMU reports that KVM is unavailable, confirm that
221+
hardware virtualization is enabled and that `/dev/kvm` is writable by the VMM
222+
user.

0 commit comments

Comments
 (0)