Skip to content

Commit 513926e

Browse files
committed
docs: document management and admin API authentication (#796)
1 parent abcc653 commit 513926e

14 files changed

Lines changed: 109 additions & 13 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
- shared API authentication (`dstack-api-auth`) protecting the full VMM HTTP/pRPC/UI surface and unifying Gateway/KMS admin auth: bearer/`X-Admin-Token`/HTTP Basic/bcrypt htpasswd, constant-time verification (#796)
12+
813
## [0.5.5] - 2025-10-20
914

1015
### Added

docs/deployment.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ reuse = true
7474
image_path = "./images"
7575
run_path = "./run/vm"
7676

77+
[auth]
78+
enabled = true
79+
# generate with: openssl rand -hex 32
80+
tokens = ["<paste output of: openssl rand -hex 32>"]
81+
7782
[cvm]
7883
kms_urls = []
7984
gateway_urls = []
@@ -93,6 +98,10 @@ address = "vsock:2"
9398
port = 10000
9499
```
95100

101+
> **VMM API authentication (required for non-localhost binds).** Since [PR #796](https://github.com/Dstack-TEE/dstack/pull/796), the `[auth]` token guards the *entire* VMM surface — creating and stopping CVMs, the web UI, and all pRPC calls — not just `/logs`. Because the example above binds `tcp:0.0.0.0:9080` (reachable off the host), you MUST enable `[auth]`; otherwise the whole control API is exposed unauthenticated. Auth is fail-closed: with `enabled = true` and no usable credential, requests are rejected.
102+
>
103+
> Clients authenticate with `Authorization: Bearer <token>` (or the `X-Admin-Token: <token>` header). Instead of inline `tokens`, you can point to an Apache bcrypt htpasswd file with `htpasswd_file = "/etc/dstack/admin.htpasswd"` (create it with `htpasswd -B -c /etc/dstack/admin.htpasswd admin`). Only bcrypt (`-B`) entries are accepted.
104+
96105
Download guest images from [dstack guest-OS releases](https://github.com/Dstack-TEE/dstack/releases) and extract to `./images/`.
97106

98107
> For reproducible builds and verification, see the [Security Model](./security/security-model.md).
@@ -113,6 +122,8 @@ Production KMS requires:
113122

114123
#### Auth Server Options
115124

125+
> **Note:** the boot-authorization webhook below (auth-simple / auth-eth) is a *different* mechanism from the KMS admin-API authentication. The webhook allowlists which CVMs may boot and receive keys; the admin API (`admin_token_hash` in `kms.toml`) guards operator RPCs. See [dstack-kms admin authentication](../dstack/kms/README.md#admin-api-authentication).
126+
116127
| Server | Use Case | Configuration |
117128
|--------|----------|---------------|
118129
| [auth-simple](../dstack/kms/auth-simple/) | Config-file-based whitelisting | JSON config file |
@@ -428,6 +439,8 @@ curl http://<new-kms>:9203/finish
428439
>
429440
> If you skip this, `Onboard.Onboard` or later trusted RPCs will fail with KMS authorization errors.
430441
442+
> **Admin authentication.** Onboarding itself is gated by attestation and the authorization backend above — not by a token. Separately, the KMS *admin* RPCs (for example clearing the image cache) are guarded by an admin token: set `admin_token_hash` (SHA-256 hex of your token) in the KMS `kms.toml` and send the token on admin requests via `Authorization: Bearer <token>`. An empty hash denies all admin RPCs (fail-closed). See [dstack-kms admin authentication](../dstack/kms/README.md#admin-api-authentication).
443+
431444
---
432445

433446
## Deploying Apps

docs/dstack-gateway.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,28 @@ Open `vmm.toml` and adjust dstack-gateway configuration in the `gateway` section
6363

6464
- `base_domain`: Same as `base_domain` from `gateway.toml`'s `core.proxy` section
6565
- `port`: Same as `listen_port` from `gateway.toml`'s `core.proxy` section
66+
67+
## Admin API authentication
68+
69+
The gateway exposes a separate admin API (used for sync, WireGuard peer management, and other operator RPCs). Configure it in the `core.admin` section of `gateway.toml`:
70+
71+
```toml
72+
[core.admin]
73+
enabled = true
74+
address = "0.0.0.0:9016"
75+
# generate with: openssl rand -hex 32
76+
admin_token = "<paste output of: openssl rand -hex 32>"
77+
# alternatively, an Apache bcrypt htpasswd file (htpasswd -B -c admin.htpasswd admin)
78+
# htpasswd_file = "/etc/dstack/gateway-admin.htpasswd"
79+
insecure_no_auth = false
80+
```
81+
82+
- `enabled`: enable the admin API server.
83+
- `address`: bind address/port for the admin API.
84+
- `admin_token`: shared admin token. It can also be supplied via the environment variables `DSTACK_GATEWAY_ADMIN_TOKEN` or `ADMIN_API_TOKEN` instead of the config file.
85+
- `htpasswd_file`: path to an Apache bcrypt htpasswd file (create with `htpasswd -B -c admin.htpasswd admin`); only bcrypt entries are accepted. Can be used instead of, or alongside, `admin_token`.
86+
- `insecure_no_auth`: development-only escape hatch that disables admin authentication. Never enable it on a network-reachable admin interface.
87+
88+
The admin server is fail-closed: if it is enabled with no `admin_token` and no `htpasswd_file`, and `insecure_no_auth` is `false`, it refuses to start rather than exposing an unauthenticated admin API.
89+
90+
Clients authenticate by sending `Authorization: Bearer <token>` or the `X-Admin-Token: <token>` header.

docs/security/security-best-practices.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ The KMS TLS listener may keep `rpc.tls.mutual.mandatory = false` because bootstr
9393

9494
App key release and KMS key handover still require verified caller attestation from the RA-TLS client certificate. Certificate signing verifies the CSR signature and embedded attestation before signing.
9595

96+
## Management/admin API authentication
97+
98+
The VMM, gateway, and KMS management surfaces must have authentication enabled in production:
99+
100+
- VMM: set `[auth] enabled = true` with `tokens` (or `htpasswd_file`) — this guards the entire VMM HTTP/pRPC/UI surface. Never bind to a non-localhost address without it.
101+
- Gateway: set `[core.admin] admin_token` (or `htpasswd_file`) and keep `insecure_no_auth = false`.
102+
- KMS: set `admin_token_hash` (SHA-256 hex of the admin token); an empty hash denies all admin RPCs.
103+
104+
Only bcrypt htpasswd entries are accepted (create with `htpasswd -B`), and credential comparisons are constant-time. Clients authenticate with `Authorization: Bearer <token>` (or `X-Admin-Token`). All three are fail-closed.
105+
96106
## Keep private material owner-only
97107

98108
Secret-bearing files should be owner-only (`0600`) wherever possible, including app keys, decrypted env files, KMS root keys, gateway WireGuard/TLS keys, and ACME credentials. Preserve restrictive permissions when copying volumes, backing up `/etc/kms/certs`, or moving gateway and certbot state between hosts. Public issue [#606](https://github.com/Dstack-TEE/dstack/issues/606) tracks the remaining low-cost hardening work in dstack-managed file writes.

docs/tutorials/attestation-verification.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Verify you have a running CVM:
129129

130130
```bash
131131
cd ~/dstack/dstack/vmm
132+
export DSTACK_VMM_AUTH_USER=admin
132133
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
133134
./src/vmm-cli.py --url http://127.0.0.1:9080 lsvm
134135
```
@@ -141,6 +142,7 @@ The VMM provides a `/guest/Info` endpoint that proxies into the CVM and retrieve
141142

142143
```bash
143144
cd ~/dstack/dstack/vmm
145+
export DSTACK_VMM_AUTH_USER=admin
144146
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
145147

146148
# Get the VM UUID for hello-world
@@ -384,6 +386,7 @@ Compare the CVM's actual measurements against your expected values:
384386
# verify-measurements.sh
385387

386388
cd ~/dstack/dstack/vmm
389+
export DSTACK_VMM_AUTH_USER=admin
387390
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
388391

389392
# Get VM UUID
@@ -576,6 +579,7 @@ echo "Image: $IMAGE_VERSION"
576579
echo ""
577580

578581
cd ~/dstack/dstack/vmm
582+
export DSTACK_VMM_AUTH_USER=admin
579583
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
580584

581585
# --- Step 1: Get VM UUID ---

docs/tutorials/gateway-build-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ Now generate the VMM deployment manifest:
460460
```bash
461461
cd ~/dstack/dstack/vmm
462462

463+
export DSTACK_VMM_AUTH_USER=admin
463464
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
464465

465466
./src/vmm-cli.py --url http://127.0.0.1:9080 compose \

docs/tutorials/gateway-service-setup.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ cd ~/gateway-deploy
8585
set -a; source .env; set +a
8686

8787
cd ~/dstack/dstack/vmm
88+
export DSTACK_VMM_AUTH_USER=admin
8889
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
8990

9091
./src/vmm-cli.py --url http://127.0.0.1:9080 deploy \
@@ -408,6 +409,7 @@ Navigate to the VMM directory first:
408409

409410
```bash
410411
cd ~/dstack/dstack/vmm
412+
export DSTACK_VMM_AUTH_USER=admin
411413
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
412414
```
413415

docs/tutorials/hello-world-app.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ Use `vmm-cli.py compose` to generate the encrypted deployment manifest. The `--g
184184

185185
```bash
186186
cd ~/dstack/dstack/vmm
187+
export DSTACK_VMM_AUTH_USER=admin
187188
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
188189

189190
./src/vmm-cli.py --url http://127.0.0.1:9080 compose \
@@ -265,6 +266,7 @@ ssh user@your-server
265266

266267
```bash
267268
cd ~/dstack/dstack/vmm
269+
export DSTACK_VMM_AUTH_USER=admin
268270
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
269271

270272
SRV_DOMAIN=$(grep ^SRV_DOMAIN ~/gateway-deploy/.env | cut -d= -f2)
@@ -373,6 +375,7 @@ Navigate to the VMM directory:
373375

374376
```bash
375377
cd ~/dstack/dstack/vmm
378+
export DSTACK_VMM_AUTH_USER=admin
376379
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
377380
```
378381

docs/tutorials/kms-cvm-deployment.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ Use the VMM CLI tool to deploy the CVM:
256256
cd ~/dstack/dstack/vmm
257257
258258
# Set VMM auth from saved token
259+
export DSTACK_VMM_AUTH_USER=admin
259260
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
260261
261262
# Generate app-compose.json with local key provider enabled
@@ -306,7 +307,7 @@ curl -s -H "Authorization: Bearer $(cat ~/.dstack/secrets/vmm-auth-token)" \
306307
"http://127.0.0.1:9080/logs?id=VM_ID&follow=true&ansi=false"
307308
```
308309

309-
> **Note:** The VMM logs endpoint requires Bearer token authentication. The `vmm-cli.py logs` command may not work with token auth — use curl directly as shown above.
310+
> **Note:** The VMM logs endpoint requires authentication. `vmm-cli.py logs` sends credentials automatically when `DSTACK_VMM_TOKEN` (or `--token`) is set, so it works against an auth-enabled VMM; the curl form above is an equivalent alternative.
310311
311312
Look for these log messages indicating KMS entered onboard mode:
312313
```

docs/tutorials/troubleshooting-first-application.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ If `/guest/Info` returns empty or errors, check that the CVM is running:
8484

8585
```bash
8686
cd ~/dstack/dstack/vmm
87+
export DSTACK_VMM_AUTH_USER=admin
8788
export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token)
8889
./src/vmm-cli.py --url http://127.0.0.1:9080 lsvm
8990
```

0 commit comments

Comments
 (0)