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
14 changes: 14 additions & 0 deletions dstack/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 dstack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ scale = { version = "3.7.4", package = "parity-scale-codec", features = [
] }
serde = { version = "1.0.228", features = ["derive"], default-features = false }
serde-human-bytes = "0.1.2"
serde_with = "3.14.0"
semver = "1.0.28"
serde_jcs = "0.2.0"
rmp-serde = "1.3.1"
Expand Down
6 changes: 3 additions & 3 deletions dstack/dstack-attest/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,7 @@ struct Mrs {
fn key_provider_info_from_mr_config(mr_config: &MrConfigV3) -> Result<Vec<u8>> {
serde_json::to_vec(&KeyProviderInfo::new(
mr_config.key_provider_name().to_string(),
hex::encode(&mr_config.key_provider_id),
hex::encode(mr_config.key_provider_id.as_deref().unwrap_or_default()),
))
.context("Failed to serialize key provider info")
}
Expand Down Expand Up @@ -1607,8 +1607,8 @@ fn decode_app_info_sev_snp(
let mrs = decode_mr_sev_snp(&parsed.measurement, &parsed.host_data);

Ok(AppInfo {
app_id: mr_config.app_id,
instance_id: mr_config.instance_id,
app_id: mr_config.app_id.unwrap_or_default(),
instance_id: mr_config.instance_id.unwrap_or_default(),
device_id: sha256(parsed.chip_id).to_vec(),
mr_system: mrs.mr_system,
mr_aggregated: mrs.mr_aggregated,
Expand Down
2 changes: 1 addition & 1 deletion dstack/dstack-attest/tests/sev_snp_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn verify_sev_snp_attestation_bin() {
);
// The HOST_DATA-bound app identity is recovered from the mr_config document.
assert_eq!(
hex::encode(&binding.mr_config.app_id),
hex::encode(binding.mr_config.app_id.as_deref().unwrap_or_default()),
"86e59625be93207bc2351c4d1bba20037cec8e16",
"mr_config app_id bound by HOST_DATA"
);
Expand Down
8 changes: 5 additions & 3 deletions dstack/dstack-mr/src/sev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -911,13 +911,15 @@ pub fn validate_mr_config(mr_config: &MrConfigV3) -> Result<()> {
if mr_config.version != 3 {
bail!("mr_config version must be 3");
}
ensure_len("mr_config.app_id", &mr_config.app_id, 20)?;
if let Some(app_id) = mr_config.app_id.as_deref() {
ensure_len("mr_config.app_id", app_id, 20)?;
}
ensure_len("mr_config.compose_hash", &mr_config.compose_hash, 32)?;
if let Some(gpu_policy_hash) = &mr_config.gpu_policy_hash {
ensure_len("mr_config.gpu_policy_hash", gpu_policy_hash, 32)?;
}
if !mr_config.instance_id.is_empty() {
ensure_len("mr_config.instance_id", &mr_config.instance_id, 20)?;
if let Some(instance_id) = mr_config.instance_id.as_deref() {
ensure_len("mr_config.instance_id", instance_id, 20)?;
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions dstack/dstack-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ or-panic.workspace = true
scale = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
serde-human-bytes.workspace = true
serde_with.workspace = true
serde_jcs.workspace = true
serde_json.workspace = true
sha2.workspace = true
Expand Down
32 changes: 23 additions & 9 deletions dstack/dstack-types/src/mr_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use or_panic::ResultOrPanic;
use serde::{Deserialize, Serialize};
use serde_human_bytes as hex_bytes;
use serde_with::skip_serializing_none;
use sha2::Sha256;
use sha3::{Digest, Keccak256};
use std::{error::Error, fmt};
Expand Down Expand Up @@ -100,24 +101,26 @@ impl From<serde_json::Error> for MrConfigDocumentError {
}
}

#[skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MrConfigV3 {
#[serde(default = "mr_config_v3_version")]
pub version: u8,
#[serde(with = "hex_bytes")]
pub app_id: Vec<u8>,
/// Optional application identity pin.
#[serde(default, with = "hex_bytes")]
pub app_id: Option<Vec<u8>>,
#[serde(with = "hex_bytes")]
pub compose_hash: Vec<u8>,
/// Hash of the raw application GPU policy. GPU launches populate it;
/// non-GPU and historical v3 launch documents omit it.
#[serde(default, skip_serializing_if = "Option::is_none", with = "hex_bytes")]
#[serde(default, with = "hex_bytes")]
pub gpu_policy_hash: Option<Vec<u8>>,
pub key_provider: KeyProviderKind,
#[serde(default, with = "hex_bytes")]
pub key_provider_id: Vec<u8>,
pub key_provider_id: Option<Vec<u8>>,
#[serde(default, with = "hex_bytes")]
pub instance_id: Vec<u8>,
pub instance_id: Option<Vec<u8>>,
}

impl MrConfigV3 {
Expand All @@ -131,12 +134,12 @@ impl MrConfigV3 {
) -> Self {
Self {
version: mr_config_v3_version(),
app_id,
app_id: (!app_id.is_empty()).then_some(app_id),
compose_hash,
gpu_policy_hash,
key_provider,
key_provider_id,
instance_id,
key_provider_id: (!key_provider_id.is_empty()).then_some(key_provider_id),
instance_id: (!instance_id.is_empty()).then_some(instance_id),
}
}

Expand Down Expand Up @@ -203,7 +206,7 @@ mod tests {
vec![0x44; 20],
);
let mut changed = config.clone();
changed.app_id[0] ^= 0xff;
changed.app_id.as_mut().expect("app_id is set")[0] ^= 0xff;

assert_ne!(config.to_snp_host_data(), changed.to_snp_host_data());
assert_eq!(config.to_snp_host_data().len(), 32);
Expand Down Expand Up @@ -235,6 +238,17 @@ mod tests {
Ok(())
}

#[test]
fn mr_config_v3_defaults_missing_app_id_to_empty() -> Result<(), Box<dyn Error>> {
let config = MrConfigV3::from_document(
r#"{"compose_hash":"2222222222222222222222222222222222222222222222222222222222222222","key_provider":"none"}"#,
)?;

assert!(config.app_id.is_none());
assert!(!config.to_canonical_json().contains("app_id"));
Ok(())
}

#[test]
fn mr_config_v3_generates_jcs_but_hashes_document_bytes() -> Result<(), Box<dyn Error>> {
let config = MrConfigV3::new(
Expand Down
47 changes: 41 additions & 6 deletions dstack/dstack-util/src/system_setup/config_id_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,17 +168,23 @@ fn verify_mr_config_v3_document(
bail!("Invalid mr_config gpu_policy_hash");
}
}
if mr_config.app_id.as_slice() != local.app_id {
bail!("Invalid mr_config app_id");
if let Some(app_id) = mr_config.app_id.as_deref() {
if app_id != local.app_id {
bail!("Invalid mr_config app_id");
}
}
if mr_config.instance_id.as_slice() != local.instance_id {
bail!("Invalid mr_config instance_id");
if let Some(instance_id) = mr_config.instance_id.as_deref() {
if instance_id != local.instance_id {
bail!("Invalid mr_config instance_id");
}
}
if mr_config.key_provider != local.key_provider {
bail!("Invalid mr_config key_provider");
}
if mr_config.key_provider_id.as_slice() != local.key_provider_id {
bail!("Invalid mr_config key_provider_id");
if let Some(key_provider_id) = mr_config.key_provider_id.as_deref() {
if key_provider_id != local.key_provider_id {
bail!("Invalid mr_config key_provider_id");
}
}
Ok(mr_config)
}
Expand Down Expand Up @@ -256,6 +262,35 @@ mod tests {
}
}

#[test]
fn mr_config_v3_skips_app_id_check_when_field_is_missing() -> Result<()> {
let compose_hash = [0x22u8; 32];
let gpu_policy_hash = [0x55u8; 32];
let app_id = [0x11u8; 20];
let instance_id = [0x44u8; 20];
let key_provider_id = [0x33u8; 32];
let document = MrConfigV3::new(
Vec::new(),
compose_hash.to_vec(),
Some(gpu_policy_hash.to_vec()),
KeyProviderKind::Kms,
key_provider_id.to_vec(),
instance_id.to_vec(),
)
.to_canonical_json();
let local = LocalMrConfigValues {
compose_hash: &compose_hash,
gpu_policy_hash: &gpu_policy_hash,
app_id: &app_id,
instance_id: &instance_id,
key_provider: KeyProviderKind::Kms,
key_provider_id: &key_provider_id,
};

verify_mr_config_v3_document(&document, local)?;
Ok(())
}

#[test]
fn mr_config_v3_document_must_match_expected_gpu_policy_hash() {
let compose_hash = [0x22u8; 32];
Expand Down
6 changes: 3 additions & 3 deletions dstack/kms/src/main_service/amd_attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ fn build_amd_snp_boot_info_with_tcb_status(
mr_aggregated,
os_image_hash: os_image_hash.to_vec(),
mr_system,
app_id: mr_config.app_id.clone(),
app_id: mr_config.app_id.clone().unwrap_or_default(),
compose_hash: mr_config.compose_hash.clone(),
instance_id: mr_config.instance_id.clone(),
instance_id: mr_config.instance_id.clone().unwrap_or_default(),
device_id: verified_chip_id.to_vec(),
key_provider_info,
tcb_status: tcb_status.to_string(),
Expand Down Expand Up @@ -182,7 +182,7 @@ fn parse_measurement_input_from_vm_config(vm_config: &str) -> Result<Measurement
fn mr_config_key_provider_info(mr_config: &MrConfigV3) -> Result<Vec<u8>> {
serde_json::to_vec(&KeyProviderInfo::new(
mr_config.key_provider_name().to_string(),
hex::encode(&mr_config.key_provider_id),
hex::encode(mr_config.key_provider_id.as_deref().unwrap_or_default()),
))
.context("failed to serialize key provider info")
}
Expand Down
65 changes: 46 additions & 19 deletions dstack/vmm/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,27 +1083,12 @@ impl App {
let manifest = work_dir.manifest().context("Failed to read manifest")?;
let cfg = &self.config;
let compose_hash = sha256_file(shared_dir.join(APP_COMPOSE))?;
let platform = cfg.cvm.resolved_platform();
let app_compose = work_dir
.app_compose()
.context("Failed to get app compose")?;
let use_mr_config_v3 = !manifest.no_tee
&& (platform == crate::config::CvmPlatform::AmdSevSnp
|| (platform == crate::config::CvmPlatform::Tdx
&& cfg.cvm.use_mrconfigid
&& !app_compose.key_provider_id.is_empty()));
let mr_config = if use_mr_config_v3 {
Some(
work_dir
.prepare_mr_config_v3(
&app_compose,
manifest.gpus.as_ref().is_some_and(GpuConfig::has_gpus),
)
.context("Failed to prepare mr_config")?,
)
} else {
None
};
let mr_config = work_dir
.prepare_mr_config(&manifest, &cfg.cvm, &app_compose)
.context("Failed to prepare mr_config")?;
let sys_config_str = make_sys_config(
cfg,
&manifest,
Expand Down Expand Up @@ -1502,6 +1487,7 @@ pub(crate) fn needs_swtpm(

#[cfg(test)]
mod tests {
use super::mr_config::{mr_config_version, MrConfigVersion};
use super::*;
use crate::config::{
load_config_figment, CvmPlatform, Networking, NetworkingMode, TdxAttestationVariantConfig,
Expand Down Expand Up @@ -1742,6 +1728,47 @@ mod tests {
}
}

#[test]
fn selects_mr_config_version_for_each_tee_mode() -> Result<()> {
let manifest = test_manifest(2048);
assert_eq!(
mr_config_version(&manifest, CvmPlatform::AmdSevSnp, false, false)?,
Some(MrConfigVersion::V3)
);
assert_eq!(
mr_config_version(&manifest, CvmPlatform::Tdx, false, false)?,
None
);
assert_eq!(
mr_config_version(&manifest, CvmPlatform::Tdx, true, false)?,
Some(MrConfigVersion::V1)
);
assert_eq!(
mr_config_version(&manifest, CvmPlatform::Tdx, true, true)?,
Some(MrConfigVersion::V3)
);
assert_eq!(
mr_config_version(&manifest, CvmPlatform::Tdx, false, true)
.err()
.map(|error| error.to_string()),
Some("key provider ID requires MrConfigV3, but use_mrconfigid is disabled".to_string())
);

let mut no_tee = manifest.clone();
no_tee.no_tee = true;
assert_eq!(
mr_config_version(&no_tee, CvmPlatform::AmdSevSnp, true, true)?,
None
);

no_tee.simulated_tee = Some(dstack_types::TeeVariant::DstackAmdSevSnp);
assert_eq!(
mr_config_version(&no_tee, CvmPlatform::Tdx, false, false)?,
Some(MrConfigVersion::V3)
);
Ok(())
}

fn dummy_tdx_measurement_document() -> TdxOsImageMeasurementDocument {
let measurement = TdxOsImageMeasurement {
image: TdxImageMeasurement {
Expand Down Expand Up @@ -2112,7 +2139,7 @@ mod tests {
sys_config["nvidia_attestation_proxy_url"],
"http://10.0.2.2:8090"
);
assert_eq!(parsed_mr_config.app_id, vec![0x11; 20]);
assert_eq!(parsed_mr_config.app_id, Some(vec![0x11; 20]));
assert_eq!(parsed_mr_config.compose_hash, vec![0x22; 32]);
assert_eq!(parsed_mr_config.gpu_policy_hash, None);
assert_eq!(vm_config["mr_config"], sys_config["mr_config"]);
Expand Down
Loading
Loading