Skip to content

Commit 1cf4287

Browse files
authored
Merge pull request #1406 from Dstack-TEE/fix/kms-admin-onboard-handover
fix(kms): onboard through the source's admin listener when given a token
2 parents 5e950d9 + 88d0203 commit 1cf4287

1 file changed

Lines changed: 72 additions & 26 deletions

File tree

‎dstack/kms/src/onboard_service.rs‎

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ use std::{
99

1010
use anyhow::{bail, Context, Result};
1111
use dstack_kms_rpc::{
12+
admin_client::AdminClient,
1213
kms_client::KmsClient,
1314
onboard_server::{OnboardRpc, OnboardServer},
14-
AttestationInfoResponse, BootstrapRequest, BootstrapResponse, GetKmsKeyRequest, OnboardRequest,
15-
OnboardResponse,
15+
AttestationInfoResponse, BootstrapRequest, BootstrapResponse, GetKmsKeyRequest, KmsKeyResponse,
16+
OnboardRequest, OnboardResponse,
1617
};
1718
use fs_err as fs;
1819
use k256::ecdsa::SigningKey;
@@ -606,30 +607,68 @@ impl Keys {
606607
) -> Result<Self> {
607608
let attestation_slot = Arc::new(Mutex::new(None::<VerifiedAttestation>));
608609
let attestation_slot_out = attestation_slot.clone();
610+
let capture_source_attestation = Box::new(move |info: Option<CertInfo>| {
611+
let Some(info) = info else {
612+
bail!("Source KMS did not present a TLS certificate");
613+
};
614+
let Some(attestation) = info.attestation else {
615+
bail!("Source KMS certificate does not contain attestation");
616+
};
617+
let mut slot = attestation_slot_out
618+
.lock()
619+
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?;
620+
*slot = Some(attestation);
621+
Ok(())
622+
});
623+
let source_attestation = || {
624+
attestation_slot
625+
.lock()
626+
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?
627+
.clone()
628+
.context("Missing source KMS attestation")
629+
};
630+
631+
if !source_token.is_empty() {
632+
// `other_kms_url` is the source's admin listener. It accepts a self-issued
633+
// RA-TLS client certificate, so no temp CA is fetched first.
634+
let (ra_cert, ra_key) = gen_ra_cert(None).await?;
635+
let client = RaClientConfig::builder()
636+
.tls_no_check(true)
637+
.tls_built_in_root_certs(false)
638+
.remote_uri(other_kms_url.to_string())
639+
.tls_client_cert(ra_cert)
640+
.tls_client_key(ra_key)
641+
.cert_validator(capture_source_attestation)
642+
.attestation_verifier(attestation_verifier.clone())
643+
.bearer_token(source_token.to_string())
644+
.build()
645+
.into_client()?;
646+
let info = dstack_client().info().await.context("Failed to get info")?;
647+
let keys_res = AdminClient::new(client)
648+
.get_kms_key(GetKmsKeyRequest {
649+
vm_config: info.vm_config,
650+
})
651+
.await?;
652+
// The attestation is only captured by that first request; nothing
653+
// received is used before the source passes this check.
654+
ensure_kms_allowed(cfg, &source_attestation()?, &attestation_verifier)
655+
.await
656+
.context("Source KMS is not allowed for onboarding")?;
657+
return Self::from_key_response(cfg, keys_res, domain).await;
658+
}
659+
609660
let client = RaClientConfig::builder()
610661
.tls_no_check(true)
611662
.remote_uri(other_kms_url.to_string())
612-
.cert_validator(Box::new(move |info: Option<CertInfo>| {
613-
let Some(info) = info else {
614-
bail!("Source KMS did not present a TLS certificate");
615-
};
616-
let Some(attestation) = info.attestation else {
617-
bail!("Source KMS certificate does not contain attestation");
618-
};
619-
let mut slot = attestation_slot_out
620-
.lock()
621-
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?;
622-
*slot = Some(attestation);
623-
Ok(())
624-
}))
663+
.cert_validator(capture_source_attestation)
625664
.attestation_verifier(attestation_verifier.clone())
626-
.maybe_bearer_token((!source_token.is_empty()).then(|| source_token.to_string()))
627665
.build()
628666
.into_client()?;
629667
let mut kms_client = KmsClient::new(client);
630668

631669
let tmp_ca = kms_client.get_temp_ca_cert().await?;
632-
let (ra_cert, ra_key) = gen_ra_cert(tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key).await?;
670+
let (ra_cert, ra_key) =
671+
gen_ra_cert(Some((tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key))).await?;
633672
let ra_client = RaClient::new_mtls(
634673
other_kms_url.into(),
635674
ra_cert,
@@ -638,12 +677,7 @@ impl Keys {
638677
)
639678
.context("Failed to create client")?;
640679
kms_client = KmsClient::new(ra_client);
641-
let source_attestation = attestation_slot
642-
.lock()
643-
.map_err(|_| anyhow::anyhow!("source attestation mutex poisoned"))?
644-
.clone()
645-
.context("Missing source KMS attestation")?;
646-
ensure_kms_allowed(cfg, &source_attestation, &attestation_verifier)
680+
ensure_kms_allowed(cfg, &source_attestation()?, &attestation_verifier)
647681
.await
648682
.context("Source KMS is not allowed for onboarding")?;
649683

@@ -653,6 +687,14 @@ impl Keys {
653687
vm_config: info.vm_config,
654688
})
655689
.await?;
690+
Self::from_key_response(cfg, keys_res, domain).await
691+
}
692+
693+
async fn from_key_response(
694+
cfg: &KmsConfig,
695+
keys_res: KmsKeyResponse,
696+
domain: &str,
697+
) -> Result<Self> {
656698
if keys_res.keys.len() != 1 {
657699
return Err(anyhow::anyhow!("Invalid keys"));
658700
}
@@ -818,11 +860,11 @@ fn keccak256(msg: &[u8]) -> [u8; 32] {
818860
hasher.finalize().into()
819861
}
820862

821-
async fn gen_ra_cert(ca_cert_pem: String, ca_key_pem: String) -> Result<(String, String)> {
863+
/// Mint an RA-TLS client certificate, signed by `ca` (cert and key PEM) or self-issued.
864+
async fn gen_ra_cert(ca: Option<(String, String)>) -> Result<(String, String)> {
822865
use ra_tls::cert::CertRequest;
823866
use ra_tls::rcgen::{KeyPair, PKCS_ECDSA_P256_SHA256};
824867

825-
let ca = CaCert::new(ca_cert_pem, ca_key_pem)?;
826868
let key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
827869
let pubkey = key.public_key_der();
828870
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
@@ -836,6 +878,10 @@ async fn gen_ra_cert(ca_cert_pem: String, ca_key_pem: String) -> Result<(String,
836878
.attestation(&attestation)
837879
.key(&key)
838880
.build();
839-
let cert = ca.sign(req).context("Failed to sign certificate")?;
881+
let cert = match ca {
882+
Some((cert_pem, key_pem)) => CaCert::new(cert_pem, key_pem)?.sign(req),
883+
None => req.self_signed(),
884+
}
885+
.context("Failed to sign certificate")?;
840886
Ok((cert.pem(), key.serialize_pem()))
841887
}

0 commit comments

Comments
 (0)