|
| 1 | +// SPDX-FileCopyrightText: © 2025 Phala Network <dstack@phala.network> |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +use std::sync::Arc; |
| 6 | + |
| 7 | +use anyhow::{bail, Context, Result}; |
| 8 | +use clap::Parser; |
| 9 | +use dstack_guest_agent::{ |
| 10 | + backend::{ |
| 11 | + load_versioned_attestation, simulated_attest_response, simulated_info_attestation, |
| 12 | + simulated_quote_response, PlatformBackend, |
| 13 | + }, |
| 14 | + config::{self, Config}, |
| 15 | + AppState, run_server, |
| 16 | +}; |
| 17 | +use dstack_guest_agent_rpc::{AttestResponse, GetQuoteResponse}; |
| 18 | +use ra_rpc::Attestation; |
| 19 | +use ra_tls::attestation::VersionedAttestation; |
| 20 | +use serde::Deserialize; |
| 21 | +use tracing::warn; |
| 22 | + |
| 23 | +const DEFAULT_CONFIG: &str = include_str!("../dstack.toml"); |
| 24 | + |
| 25 | +#[derive(Parser)] |
| 26 | +#[command(author, version, about = "dstack guest agent simulator", long_version = dstack_guest_agent::app_version())] |
| 27 | +struct Args { |
| 28 | + /// Path to the configuration file |
| 29 | + #[arg(short, long)] |
| 30 | + config: Option<String>, |
| 31 | + |
| 32 | + /// Enable systemd watchdog |
| 33 | + #[arg(short, long)] |
| 34 | + watchdog: bool, |
| 35 | +} |
| 36 | + |
| 37 | +#[derive(Debug, Clone, Deserialize)] |
| 38 | +struct SimulatorSettings { |
| 39 | + attestation_file: String, |
| 40 | +} |
| 41 | + |
| 42 | +#[derive(Debug, Clone, Deserialize)] |
| 43 | +struct SimulatorCoreConfig { |
| 44 | + #[serde(flatten)] |
| 45 | + core: Config, |
| 46 | + simulator: SimulatorSettings, |
| 47 | +} |
| 48 | + |
| 49 | +struct SimulatorPlatform { |
| 50 | + attestation: VersionedAttestation, |
| 51 | +} |
| 52 | + |
| 53 | +impl SimulatorPlatform { |
| 54 | + fn new(attestation: VersionedAttestation) -> Self { |
| 55 | + Self { attestation } |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +impl PlatformBackend for SimulatorPlatform { |
| 60 | + fn attestation_for_info(&self) -> Result<Option<Attestation>> { |
| 61 | + Ok(Some(simulated_info_attestation(&self.attestation))) |
| 62 | + } |
| 63 | + |
| 64 | + fn attestation_override(&self) -> Result<Option<VersionedAttestation>> { |
| 65 | + Ok(Some(self.attestation.clone())) |
| 66 | + } |
| 67 | + |
| 68 | + fn quote_response(&self, report_data: [u8; 64], vm_config: &str) -> Result<GetQuoteResponse> { |
| 69 | + simulated_quote_response(&self.attestation, report_data, vm_config) |
| 70 | + } |
| 71 | + |
| 72 | + fn attest_response(&self, _report_data: [u8; 64]) -> Result<AttestResponse> { |
| 73 | + Ok(simulated_attest_response(&self.attestation)) |
| 74 | + } |
| 75 | + |
| 76 | + fn emit_event(&self, event: &str, _payload: &[u8]) -> Result<()> { |
| 77 | + bail!("runtime event emission is unavailable in simulator mode: {event}") |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +#[rocket::main] |
| 82 | +async fn main() -> Result<()> { |
| 83 | + { |
| 84 | + use tracing_subscriber::{fmt, EnvFilter}; |
| 85 | + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); |
| 86 | + fmt().with_env_filter(filter).with_ansi(false).init(); |
| 87 | + } |
| 88 | + let args = Args::parse(); |
| 89 | + let figment = config::load_config_figment_with_default(DEFAULT_CONFIG, args.config.as_deref()); |
| 90 | + let sim_config: SimulatorCoreConfig = figment |
| 91 | + .focus("core") |
| 92 | + .extract() |
| 93 | + .context("Failed to extract simulator core config")?; |
| 94 | + warn!(attestation_file = %sim_config.simulator.attestation_file, "starting dstack guest-agent simulator"); |
| 95 | + let attestation = load_versioned_attestation(&sim_config.simulator.attestation_file)?; |
| 96 | + let state = AppState::new(sim_config.core, Arc::new(SimulatorPlatform::new(attestation))) |
| 97 | + .await |
| 98 | + .context("Failed to create simulator app state")?; |
| 99 | + run_server(state, figment, args.watchdog).await |
| 100 | +} |
| 101 | + |
| 102 | + |
| 103 | +#[cfg(test)] |
| 104 | +mod tests { |
| 105 | + use super::*; |
| 106 | + |
| 107 | + fn load_fixture_platform() -> SimulatorPlatform { |
| 108 | + let fixture = load_versioned_attestation( |
| 109 | + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../guest-agent/fixtures/attestation.bin"), |
| 110 | + ) |
| 111 | + .expect("fixture attestation should load"); |
| 112 | + SimulatorPlatform::new(fixture) |
| 113 | + } |
| 114 | + |
| 115 | + #[test] |
| 116 | + fn simulator_rejects_runtime_event_emission() { |
| 117 | + let platform = load_fixture_platform(); |
| 118 | + let err = platform.emit_event("test.event", b"payload").unwrap_err(); |
| 119 | + assert!(err.to_string().contains("unavailable in simulator mode")); |
| 120 | + } |
| 121 | + |
| 122 | + #[test] |
| 123 | + fn simulator_provides_attestation_override() { |
| 124 | + let platform = load_fixture_platform(); |
| 125 | + assert!(platform.attestation_override().unwrap().is_some()); |
| 126 | + assert!(platform.attestation_for_info().unwrap().is_some()); |
| 127 | + } |
| 128 | +} |
0 commit comments