Skip to content

Commit be9d047

Browse files
committed
Merge branch 'configid'
2 parents fc51424 + af8c8bf commit be9d047

File tree

9 files changed

+81
-2
lines changed

9 files changed

+81
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub struct AppCompose {
2727
pub local_key_provider_enabled: bool,
2828
#[serde(default)]
2929
pub key_provider: Option<KeyProviderKind>,
30+
#[serde(default, with = "hex_bytes")]
31+
pub key_provider_id: Vec<u8>,
3032
#[serde(default)]
3133
pub allowed_envs: Vec<String>,
3234
#[serde(default)]

dstack-util/src/system_setup.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::{
55
};
66

77
use anyhow::{anyhow, bail, Context, Result};
8+
use dcap_qvl::quote::{Quote, Report};
89
use dstack_kms_rpc as rpc;
910
use dstack_types::{
1011
shared_filenames::{
@@ -17,7 +18,7 @@ use fs_err as fs;
1718
use ra_rpc::client::{CertInfo, RaClient, RaClientConfig};
1819
use ra_tls::cert::generate_ra_cert;
1920
use serde::{Deserialize, Serialize};
20-
use tdx_attest::extend_rtmr3;
21+
use tdx_attest::{extend_rtmr3, get_quote};
2122
use tracing::{info, warn};
2223

2324
use crate::{
@@ -364,6 +365,7 @@ impl<'a> Stage0<'a> {
364365
let (_, ca_pem) = x509_parser::pem::parse_x509_pem(keys.ca_cert.as_bytes())
365366
.context("Failed to parse ca cert")?;
366367
let x509 = ca_pem.parse_x509().context("Failed to parse ca cert")?;
368+
self.ensure_provider_id_matches(x509.public_key().raw)?;
367369
let id = hex::encode(x509.public_key().raw);
368370
let provider_info = KeyProviderInfo::new("kms".into(), id);
369371
emit_key_provider_info(&provider_info)?;
@@ -373,6 +375,20 @@ impl<'a> Stage0<'a> {
373375
Ok(())
374376
}
375377

378+
fn ensure_provider_id_matches(&self, provider_id: &[u8]) -> Result<()> {
379+
let expected_key_provider_id = &self.shared.app_compose.key_provider_id;
380+
if expected_key_provider_id.is_empty() {
381+
return Ok(());
382+
};
383+
if expected_key_provider_id != provider_id {
384+
bail!(
385+
"Unexpected key provider id: {:?}, expected: {:?}",
386+
hex_fmt::HexFmt(provider_id),
387+
hex_fmt::HexFmt(expected_key_provider_id)
388+
);
389+
}
390+
Ok(())
391+
}
376392
async fn get_keys_from_local_key_provider(&self) -> Result<()> {
377393
info!("Getting keys from local key provider");
378394
let provision = self
@@ -387,6 +403,7 @@ impl<'a> Stage0<'a> {
387403
fs::write(self.app_keys_file(), keys_json).context("Failed to write app keys")?;
388404

389405
// write to RTMR
406+
self.ensure_provider_id_matches(&provision.mr)?;
390407
let provider_info = KeyProviderInfo::new("local-sgx".into(), hex::encode(provision.mr));
391408
emit_key_provider_info(&provider_info)?;
392409
Ok(())
@@ -470,6 +487,8 @@ impl<'a> Stage0<'a> {
470487
let key_provider = self.shared.app_compose.key_provider();
471488
let mut instance_info = self.shared.instance_info.clone();
472489

490+
validate_compose_hash(&compose_hash).context("Failed to validate compose hash")?;
491+
473492
if instance_info.app_id.is_empty() {
474493
instance_info.app_id = truncated_compose_hash.to_vec();
475494
}
@@ -536,6 +555,25 @@ impl<'a> Stage0<'a> {
536555
}
537556
}
538557

558+
fn validate_compose_hash(compose_hash: &[u8]) -> Result<()> {
559+
// If configid is not all zero, use it as compose_hash
560+
let (_, quote) = get_quote(&[0u8; 64], None).context("Failed to get quote")?;
561+
let quote = Quote::parse(&quote).context("Failed to parse quote")?;
562+
let configid = match quote.report {
563+
Report::SgxEnclave(_report) => bail!("SGX quote is not supported"),
564+
Report::TD10(report) => report.mr_config_id,
565+
Report::TD15(report) => report.base.mr_config_id,
566+
};
567+
info!("mr_config_id: {}", hex_fmt::HexFmt(&configid));
568+
if configid == [0u8; 48] {
569+
return Ok(());
570+
}
571+
if &configid[..32] != compose_hash {
572+
bail!("mr_config_id does not match compose hash");
573+
}
574+
Ok(())
575+
}
576+
539577
impl Stage1<'_> {
540578
fn resolve(&self, path: &str) -> String {
541579
path.to_string()

vmm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ key-provider-client.workspace = true
4242
dstack-types.workspace = true
4343
hex_fmt.workspace = true
4444
lspci.workspace = true
45+
base64.workspace = true
4546

4647
[dev-dependencies]
4748
insta.workspace = true

vmm/src/app/qemu.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{
1414

1515
use super::{image::Image, GpuConfig, VmState};
1616
use anyhow::{bail, Context, Result};
17+
use base64::prelude::*;
1718
use bon::Builder;
1819
use dstack_types::{
1920
shared_filenames::{APP_COMPOSE, ENCRYPTED_ENV, INSTANCE_INFO, USER_CONFIG},
@@ -301,7 +302,19 @@ impl VmConfig {
301302
command
302303
.arg("-machine")
303304
.arg("q35,kernel-irqchip=split,confidential-guest-support=tdx,hpet=off");
304-
command.arg("-object").arg("tdx-guest,id=tdx");
305+
306+
let tdx_object = if cfg.use_mrconfigid {
307+
let mut compose_hash = workdir
308+
.app_compose_hash()
309+
.context("Failed to get compose hash")?;
310+
compose_hash.resize(48, 0);
311+
let mrconfigid = BASE64_STANDARD.encode(&compose_hash);
312+
format!("tdx-guest,id=tdx,mrconfigid={mrconfigid}")
313+
} else {
314+
"tdx-guest,id=tdx".to_string()
315+
};
316+
command.arg("-object").arg(tdx_object);
317+
305318
command
306319
.arg("-device")
307320
.arg(format!("vhost-vsock-pci,guest-cid={}", self.cid));
@@ -611,6 +624,13 @@ impl VmWorkDir {
611624
self.shared_dir().join(APP_COMPOSE)
612625
}
613626

627+
pub fn app_compose_hash(&self) -> Result<Vec<u8>> {
628+
use sha2::Digest;
629+
let compose_path = self.app_compose_path();
630+
let compose = fs::read(compose_path).context("Failed to read compose")?;
631+
Ok(sha2::Sha256::new_with_prefix(&compose).finalize().to_vec())
632+
}
633+
614634
pub fn user_config_path(&self) -> PathBuf {
615635
self.shared_dir().join(USER_CONFIG)
616636
}

vmm/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ pub struct CvmConfig {
120120

121121
/// Auto restart configuration
122122
pub auto_restart: AutoRestartConfig,
123+
124+
/// Use mrconfigid instead of compose hash
125+
pub use_mrconfigid: bool,
123126
}
124127

125128
#[derive(Debug, Clone, Deserialize)]

vmm/src/console.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,11 @@ <h2>Deploy a new instance</h2>
745745
</label>
746746
</div>
747747
</div>
748+
<div class="form-group">
749+
<label for="keyProviderId">Key Provider ID (optional)</label>
750+
<input id="keyProviderId" v-model="vmForm.key_provider_id" type="text"
751+
placeholder="Key provider ID if you want to bind to a specific key provider">
752+
</div>
748753

749754
<div class="form-group full-width" v-if="vmForm.kms_enabled">
750755
<encrypted-env-editor :env-vars="vmForm.encryptedEnvs"></encrypted-env-editor>
@@ -1437,6 +1442,7 @@ <h3>Derive VM</h3>
14371442
app_id: '',
14381443
kms_enabled: true,
14391444
local_key_provider_enabled: false,
1445+
key_provider_id: '',
14401446
gateway_enabled: true,
14411447
public_logs: true,
14421448
public_sysinfo: true,
@@ -1604,6 +1610,7 @@ <h3>Derive VM</h3>
16041610
"public_sysinfo": vmForm.value.public_sysinfo,
16051611
"public_tcbinfo": vmForm.value.public_tcbinfo,
16061612
"local_key_provider_enabled": vmForm.value.local_key_provider_enabled,
1613+
"key_provider_id": vmForm.value.key_provider_id,
16071614
"allowed_envs": vmForm.value.encryptedEnvs.map(env => env.key),
16081615
"no_instance_id": !vmForm.value.gateway_enabled,
16091616
"secure_time": false,
@@ -2289,6 +2296,7 @@ <h3>Derive VM</h3>
22892296
() => vmForm.value.public_sysinfo,
22902297
() => vmForm.value.public_tcbinfo,
22912298
() => vmForm.value.local_key_provider_enabled,
2299+
() => vmForm.value.key_provider_id,
22922300
() => vmForm.value.docker_config.enabled,
22932301
() => vmForm.value.docker_config.username,
22942302
() => vmForm.value.docker_config.token_key

vmm/src/vmm-cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ def create_app_compose(self,
404404
kms_enabled: bool,
405405
gateway_enabled: bool,
406406
local_key_provider_enabled: bool,
407+
key_provider_id: str,
407408
public_logs: bool,
408409
public_sysinfo: bool,
409410
envs: Optional[Dict],
@@ -419,6 +420,7 @@ def create_app_compose(self,
419420
"kms_enabled": kms_enabled,
420421
"gateway_enabled": gateway_enabled,
421422
"local_key_provider_enabled": local_key_provider_enabled,
423+
"key_provider_id": key_provider_id,
422424
"public_logs": public_logs,
423425
"public_sysinfo": public_sysinfo,
424426
"allowed_envs": [k for k in envs.keys()],
@@ -781,6 +783,8 @@ def main():
781783
'--gateway', action='store_true', help='Enable dstack-gateway')
782784
compose_parser.add_argument(
783785
'--local-key-provider', action='store_true', help='Enable local key provider')
786+
compose_parser.add_argument(
787+
'--key-provider-id', default=None, help='Key provider ID if you want to bind to a specific key provider')
784788
compose_parser.add_argument(
785789
'--public-logs', action='store_true', help='Enable public logs')
786790
compose_parser.add_argument(
@@ -872,6 +876,7 @@ def main():
872876
kms_enabled=args.kms,
873877
gateway_enabled=args.gateway,
874878
local_key_provider_enabled=args.local_key_provider,
879+
key_provider_id=args.key_provider_id,
875880
public_logs=args.public_logs,
876881
public_sysinfo=args.public_sysinfo,
877882
envs=parse_env_file(args.env_file),

vmm/vmm.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ max_allocable_memory_in_mb = 100_000 # MB
3131
qmp_socket = false
3232
# The user to run the VM as. If empty, the VM will be run as the current user.
3333
user = ""
34+
use_mrconfigid = true
3435

3536
[cvm.port_mapping]
3637
enabled = false

0 commit comments

Comments
 (0)