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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/simulator-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

name: Simulator Release

on:
workflow_dispatch:
inputs:
version:
description: 'Release version (for example: 0.5.8)'
required: true
type: string
push:
tags:
- 'simulator-v*'

permissions:
contents: write

jobs:
build-and-release:
runs-on: ubuntu-latest
env:
TARGET_TRIPLE: x86_64-unknown-linux-musl
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Resolve version and tag
run: |
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION="${GITHUB_REF#refs/tags/simulator-v}"
fi
VERSION="${VERSION#simulator-v}"
TAG="simulator-v${VERSION}"
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "TAG=${TAG}" >> "$GITHUB_ENV"
echo "Resolved release version: ${VERSION}"

- name: Install musl toolchain
run: |
sudo apt-get update
sudo apt-get install -y musl-tools

- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ env.TARGET_TRIPLE }}

- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@v2

- name: Build musl simulator binary
run: cargo build --locked --release --target "${TARGET_TRIPLE}" -p dstack-guest-agent-simulator

- name: Package release bundle
run: ./guest-agent-simulator/package-release.sh "${VERSION}" "${TARGET_TRIPLE}"

- name: GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG }}
name: "Simulator Release v${{ env.VERSION }}"
files: |
guest-agent-simulator/dist/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
guest-agent-simulator/dist/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz.sha256
guest-agent-simulator/install-systemd.sh
body: |
## Release Assets

- `dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz`
- `dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz.sha256`
- `install-systemd.sh`

The tarball contains the musl-linked `dstack-simulator` binary together with the default
simulator config, fixture data, and a systemd unit template.

## Quick Start

Download and run directly:

```bash
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ env.TAG }}/dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
tar -xzf dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}.tar.gz
cd dstack-simulator-${{ env.VERSION }}-${{ env.TARGET_TRIPLE }}
./dstack-simulator -c dstack.toml
```

Install to systemd:

```bash
curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/${{ env.TAG }}/guest-agent-simulator/install-systemd.sh | sudo bash -s -- --version ${{ env.VERSION }}
```
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ cargo build --release -p dstack-vmm
cargo build --release -p dstack-kms
cargo build --release -p dstack-gateway
cargo build --release -p dstack-guest-agent
cargo build --release -p dstack-guest-agent-simulator

# Check code
cargo check --all-features
Expand Down
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ members = [
"iohash",
"guest-agent",
"guest-agent/rpc",
"guest-agent-simulator",
"vmm",
"vmm/rpc",
"gateway",
Expand Down
36 changes: 1 addition & 35 deletions cert-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ use anyhow::{Context, Result};
use dstack_kms_rpc::{kms_client::KmsClient, SignCertRequest};
use dstack_types::{AppKeys, KeyProvider};
use ra_rpc::client::{RaClient, RaClientConfig};
use ra_tls::{
attestation::{QuoteContentType, VersionedAttestation},
cert::{generate_ra_cert, CaCert, CertConfigV2, CertSigningRequestV2, Csr},
rcgen::KeyPair,
};
use ra_tls::cert::{generate_ra_cert, CaCert, CertSigningRequestV2};

pub enum CertRequestClient {
Local {
Expand Down Expand Up @@ -92,34 +88,4 @@ impl CertRequestClient {
}
}
}

pub async fn request_cert(
&self,
key: &KeyPair,
config: CertConfigV2,
attestation_override: Option<VersionedAttestation>,
) -> Result<Vec<String>> {
let pubkey = key.public_key_der();
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
let attestation = match attestation_override {
Some(mut attestation) => {
attestation.set_report_data(report_data);
attestation
}
None => ra_rpc::Attestation::quote(&report_data)
.context("Failed to get quote for cert pubkey")?
.into_versioned(),
};

let csr = CertSigningRequestV2 {
confirm: "please sign cert:".to_string(),
pubkey,
config,
attestation,
};
let signature = csr.signed_by(key).context("Failed to sign the CSR")?;
self.sign_csr(&csr, &signature)
.await
.context("Failed to sign the CSR")
}
}
19 changes: 0 additions & 19 deletions dstack-attest/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,25 +272,6 @@ impl VersionedAttestation {
}
}

/// Set the report_data field in the attestation and in the raw TDX quote bytes (offset 568..632).
/// This is used by the simulator to patch a canned attestation with the correct report_data
/// that binds to the actual TLS public key.
pub fn set_report_data(&mut self, report_data: [u8; 64]) {
let VersionedAttestation::V0 { attestation } = self;
attestation.report_data = report_data;
if let Some(tdx_quote) = attestation.tdx_quote_mut() {
if tdx_quote.quote.len() >= TDX_QUOTE_REPORT_DATA_RANGE.end {
tdx_quote.quote[TDX_QUOTE_REPORT_DATA_RANGE].copy_from_slice(&report_data);
} else {
tracing::warn!(
"TDX quote too short to patch report_data ({} < {})",
tdx_quote.quote.len(),
TDX_QUOTE_REPORT_DATA_RANGE.end
);
}
}
}

/// Strip data for certificate embedding (e.g. keep RTMR3 event logs only).
pub fn into_stripped(mut self) -> Self {
let VersionedAttestation::V0 { attestation } = &mut self;
Expand Down
39 changes: 33 additions & 6 deletions dstack-util/src/system_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ use luks2::{
LuksAf, LuksConfig, LuksDigest, LuksHeader, LuksJson, LuksKdf, LuksKeyslot, LuksSegment,
LuksSegmentSize,
};
use ra_rpc::client::{CertInfo, RaClient, RaClientConfig};
use ra_tls::cert::{generate_ra_cert, CertConfigV2};
use ra_rpc::{
client::{CertInfo, RaClient, RaClientConfig},
Attestation,
};
use ra_tls::{
attestation::QuoteContentType,
cert::{generate_ra_cert, CertConfigV2, CertSigningRequestV2, Csr},
};
use rand::Rng as _;
use safe_write::safe_write;
use scopeguard::defer;
Expand All @@ -53,6 +59,29 @@ use ra_tls::rcgen::{KeyPair, PKCS_ECDSA_P256_SHA256};
use serde_human_bytes as hex_bytes;
use serde_json::Value;

async fn sign_cert_request(
cert_client: &CertRequestClient,
key: &KeyPair,
config: CertConfigV2,
) -> Result<Vec<String>> {
let pubkey = key.public_key_der();
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
let attestation = Attestation::quote(&report_data)
.context("Failed to get quote for cert pubkey")?
.into_versioned();
let csr = CertSigningRequestV2 {
confirm: "please sign cert:".to_string(),
pubkey,
config,
attestation,
};
let signature = csr.signed_by(key).context("Failed to sign the CSR")?;
cert_client
.sign_csr(&csr, &signature)
.await
.context("Failed to sign the CSR")
}

mod config_id_verifier;

#[derive(clap::Parser)]
Expand Down Expand Up @@ -500,8 +529,7 @@ impl<'a> GatewayContext<'a> {
not_before: None,
not_after: Some(cert_not_after),
};
let certs = cert_client
.request_cert(&key, config, None)
let certs = sign_cert_request(&cert_client, &key, config)
.await
.context("Failed to request cert")?;
let client_cert = certs.join("\n");
Expand All @@ -520,8 +548,7 @@ impl<'a> GatewayContext<'a> {
not_before: None,
not_after: Some(cert_not_after),
};
let certs_with_quote = cert_client
.request_cert(&key, config_with_quote, None)
let certs_with_quote = sign_cert_request(&cert_client, &key, config_with_quote)
.await
.context("Failed to request cert with quote")?;
let client_cert_with_quote = certs_with_quote.join("\n");
Expand Down
1 change: 1 addition & 0 deletions guest-agent-simulator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
27 changes: 27 additions & 0 deletions guest-agent-simulator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: © 2025 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

[package]
name = "dstack-guest-agent-simulator"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true

[[bin]]
name = "dstack-simulator"
path = "src/main.rs"

[dependencies]
anyhow.workspace = true
clap.workspace = true
serde.workspace = true
serde_json.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
rocket.workspace = true
ra-rpc = { workspace = true, features = ["rocket"] }
ra-tls = { workspace = true, features = ["quote"] }
dstack-guest-agent = { path = "../guest-agent" }
dstack-guest-agent-rpc.workspace = true
22 changes: 22 additions & 0 deletions guest-agent-simulator/dstack-simulator.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

[Unit]
Description=dstack Simulator Service
After=network.target

[Service]
Type=simple
WorkingDirectory=@INSTALL_DIR@
ExecStart=@INSTALL_DIR@/dstack-simulator -c @INSTALL_DIR@/dstack.toml
Restart=on-failure
RestartSec=2s
User=@USER@
Group=@GROUP@
Environment=RUST_LOG=@RUST_LOG@
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
37 changes: 37 additions & 0 deletions guest-agent-simulator/dstack.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: © 2025 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

[default]
workers = 8
max_blocking = 64
ident = "dstack Simulator"
temp_dir = "/tmp"
keep_alive = 10
log_level = "debug"

[default.core]
keys_file = "appkeys.json"
compose_file = "app-compose.json"
sys_config_file = "sys-config.json"
data_disks = ["/"]

[default.core.simulator]
attestation_file = "attestation.bin"
patch_report_data = true

[internal-v0]
address = "unix:./tappd.sock"
reuse = true

[internal]
address = "unix:./dstack.sock"
reuse = true

[external]
address = "unix:./external.sock"
reuse = true

[guest-api]
address = "unix:./guest.sock"
reuse = true
Loading
Loading