Skip to content

Commit d3c2ca1

Browse files
committed
kms: add bootstrap and onboard KMS auth checks
1 parent 06a2347 commit d3c2ca1

3 files changed

Lines changed: 129 additions & 34 deletions

File tree

kms/src/main_service.rs

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ use ra_tls::{
2323
use scale::Decode;
2424
use sha2::Digest;
2525
use tracing::info;
26-
use upgrade_authority::BootInfo;
26+
use upgrade_authority::{build_boot_info, BootInfo};
2727

2828
use crate::{
2929
config::KmsConfig,
3030
crypto::{derive_k256_key, sign_message, sign_message_with_timestamp},
3131
};
3232

33-
mod upgrade_authority;
33+
pub(crate) mod upgrade_authority;
3434

3535
#[derive(Clone)]
3636
pub struct KmsState {
@@ -169,32 +169,7 @@ impl RpcHandler {
169169
use_boottime_mr: bool,
170170
vm_config_str: &str,
171171
) -> Result<BootConfig> {
172-
let tcb_status;
173-
let advisory_ids;
174-
match att.report.tdx_report() {
175-
Some(report) => {
176-
tcb_status = report.status.clone();
177-
advisory_ids = report.advisory_ids.clone();
178-
}
179-
None => {
180-
tcb_status = "".to_string();
181-
advisory_ids = Vec::new();
182-
}
183-
};
184-
let app_info = att.decode_app_info_ex(use_boottime_mr, vm_config_str)?;
185-
let boot_info = BootInfo {
186-
attestation_mode: att.quote.mode(),
187-
mr_aggregated: app_info.mr_aggregated.to_vec(),
188-
os_image_hash: app_info.os_image_hash,
189-
mr_system: app_info.mr_system.to_vec(),
190-
app_id: app_info.app_id,
191-
compose_hash: app_info.compose_hash,
192-
instance_id: app_info.instance_id,
193-
device_id: app_info.device_id,
194-
key_provider_info: app_info.key_provider_info,
195-
tcb_status,
196-
advisory_ids,
197-
};
172+
let boot_info = build_boot_info(att, use_boottime_mr, vm_config_str)?;
198173
let response = self
199174
.state
200175
.config

kms/src/main_service/upgrade_authority.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::config::AuthApi;
66
use anyhow::{bail, Context, Result};
77
use ra_tls::attestation::AttestationMode;
8+
use ra_tls::attestation::VerifiedAttestation;
89
use serde::de::DeserializeOwned;
910
use serde::{Deserialize, Serialize};
1011
use serde_human_bytes as hex_bytes;
@@ -33,6 +34,39 @@ pub(crate) struct BootInfo {
3334
pub advisory_ids: Vec<String>,
3435
}
3536

37+
pub(crate) fn build_boot_info(
38+
att: &VerifiedAttestation,
39+
use_boottime_mr: bool,
40+
vm_config_str: &str,
41+
) -> Result<BootInfo> {
42+
let tcb_status;
43+
let advisory_ids;
44+
match att.report.tdx_report() {
45+
Some(report) => {
46+
tcb_status = report.status.clone();
47+
advisory_ids = report.advisory_ids.clone();
48+
}
49+
None => {
50+
tcb_status = "".to_string();
51+
advisory_ids = Vec::new();
52+
}
53+
};
54+
let app_info = att.decode_app_info_ex(use_boottime_mr, vm_config_str)?;
55+
Ok(BootInfo {
56+
attestation_mode: att.quote.mode(),
57+
mr_aggregated: app_info.mr_aggregated.to_vec(),
58+
os_image_hash: app_info.os_image_hash,
59+
mr_system: app_info.mr_system.to_vec(),
60+
app_id: app_info.app_id,
61+
compose_hash: app_info.compose_hash,
62+
instance_id: app_info.instance_id,
63+
device_id: app_info.device_id,
64+
key_provider_info: app_info.key_provider_info,
65+
tcb_status,
66+
advisory_ids,
67+
})
68+
}
69+
3670
#[derive(Debug, Serialize, Deserialize)]
3771
#[serde(rename_all = "camelCase")]
3872
pub(crate) struct BootResponse {

kms/src/onboard_service.rs

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
use anyhow::{Context, Result};
5+
use std::sync::{Arc, Mutex};
6+
7+
use anyhow::{bail, Context, Result};
68
use dstack_guest_agent_rpc::{
79
dstack_guest_client::DstackGuestClient, AttestResponse, RawQuoteArgs,
810
};
@@ -15,15 +17,18 @@ use dstack_kms_rpc::{
1517
use fs_err as fs;
1618
use http_client::prpc::PrpcClient;
1719
use k256::ecdsa::SigningKey;
18-
use ra_rpc::{client::RaClient, CallContext, RpcCall};
20+
use ra_rpc::{
21+
client::{CertInfo, RaClient, RaClientConfig},
22+
CallContext, RpcCall,
23+
};
1924
use ra_tls::{
20-
attestation::{QuoteContentType, VersionedAttestation},
25+
attestation::{QuoteContentType, VerifiedAttestation, VersionedAttestation},
2126
cert::{CaCert, CertRequest},
2227
rcgen::{Certificate, KeyPair, PKCS_ECDSA_P256_SHA256},
2328
};
2429
use safe_write::safe_write;
2530

26-
use crate::config::KmsConfig;
31+
use crate::{config::KmsConfig, main_service::upgrade_authority::build_boot_info};
2732

2833
#[derive(Clone)]
2934
pub struct OnboardState {
@@ -53,6 +58,11 @@ impl RpcCall<OnboardState> for OnboardHandler {
5358
impl OnboardRpc for OnboardHandler {
5459
async fn bootstrap(self, request: BootstrapRequest) -> Result<BootstrapResponse> {
5560
let quote_enabled = self.state.config.onboard.quote_enabled;
61+
if quote_enabled {
62+
ensure_self_kms_allowed(&self.state.config)
63+
.await
64+
.context("KMS is not allowed to bootstrap")?;
65+
}
5666
let keys = Keys::generate(&request.domain, quote_enabled)
5767
.await
5868
.context("Failed to generate keys")?;
@@ -85,6 +95,7 @@ impl OnboardRpc for OnboardHandler {
8595
format!("{source_url}/prpc")
8696
};
8797
let keys = Keys::onboard(
98+
&self.state.config,
8899
&source_url,
89100
&request.domain,
90101
self.state.config.onboard.quote_enabled,
@@ -222,20 +233,55 @@ impl Keys {
222233
}
223234

224235
async fn onboard(
236+
cfg: &KmsConfig,
225237
other_kms_url: &str,
226238
domain: &str,
227239
quote_enabled: bool,
228240
pccs_url: Option<String>,
229241
) -> Result<Self> {
230-
let kms_client = RaClient::new(other_kms_url.into(), true)?;
231-
let mut kms_client = KmsClient::new(kms_client);
242+
let mut source_attestation_slot = None;
243+
let mut kms_client = if quote_enabled {
244+
let attestation_slot = Arc::new(Mutex::new(None::<VerifiedAttestation>));
245+
let attestation_slot_out = attestation_slot.clone();
246+
let client = RaClientConfig::builder()
247+
.tls_no_check(true)
248+
.remote_uri(other_kms_url.to_string())
249+
.cert_validator(Box::new(move |info: Option<CertInfo>| {
250+
let Some(info) = info else {
251+
bail!("Source KMS did not present a TLS certificate");
252+
};
253+
let Some(attestation) = info.attestation else {
254+
bail!("Source KMS certificate does not contain attestation");
255+
};
256+
*attestation_slot_out
257+
.lock()
258+
.expect("source attestation mutex poisoned") = Some(attestation);
259+
Ok(())
260+
}))
261+
.maybe_pccs_url(pccs_url.clone())
262+
.build()
263+
.into_client()?;
264+
source_attestation_slot = Some(attestation_slot);
265+
KmsClient::new(client)
266+
} else {
267+
KmsClient::new(RaClient::new(other_kms_url.into(), true)?)
268+
};
232269

233270
if quote_enabled {
234271
let tmp_ca = kms_client.get_temp_ca_cert().await?;
235272
let (ra_cert, ra_key) = gen_ra_cert(tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key).await?;
236273
let ra_client = RaClient::new_mtls(other_kms_url.into(), ra_cert, ra_key, pccs_url)
237274
.context("Failed to create client")?;
238275
kms_client = KmsClient::new(ra_client);
276+
let source_attestation = source_attestation_slot
277+
.expect("source attestation slot missing")
278+
.lock()
279+
.expect("source attestation mutex poisoned")
280+
.clone()
281+
.context("Missing source KMS attestation")?;
282+
ensure_remote_kms_allowed(cfg, &source_attestation)
283+
.await
284+
.context("Source KMS is not allowed for onboarding")?;
239285
}
240286

241287
let info = dstack_client().info().await.context("Failed to get info")?;
@@ -328,6 +374,11 @@ pub(crate) async fn update_certs(cfg: &KmsConfig) -> Result<()> {
328374
}
329375

330376
pub(crate) async fn bootstrap_keys(cfg: &KmsConfig) -> Result<()> {
377+
if cfg.onboard.quote_enabled {
378+
ensure_self_kms_allowed(cfg)
379+
.await
380+
.context("KMS is not allowed to auto-bootstrap")?;
381+
}
331382
let keys = Keys::generate(
332383
&cfg.onboard.auto_bootstrap_domain,
333384
cfg.onboard.quote_enabled,
@@ -348,6 +399,41 @@ async fn app_attest(report_data: Vec<u8>) -> Result<AttestResponse> {
348399
dstack_client().attest(RawQuoteArgs { report_data }).await
349400
}
350401

402+
async fn ensure_self_kms_allowed(cfg: &KmsConfig) -> Result<()> {
403+
let response = app_attest(pad64([0u8; 32]))
404+
.await
405+
.context("Failed to get local KMS attestation")?;
406+
let attestation = VersionedAttestation::from_scale(&response.attestation)
407+
.context("Failed to decode local KMS attestation")?
408+
.into_inner();
409+
let verified = attestation
410+
.verify(cfg.pccs_url.as_deref())
411+
.await
412+
.context("Failed to verify local KMS attestation")?;
413+
ensure_kms_allowed(cfg, &verified).await
414+
}
415+
416+
async fn ensure_remote_kms_allowed(
417+
cfg: &KmsConfig,
418+
attestation: &VerifiedAttestation,
419+
) -> Result<()> {
420+
ensure_kms_allowed(cfg, attestation).await
421+
}
422+
423+
async fn ensure_kms_allowed(cfg: &KmsConfig, attestation: &VerifiedAttestation) -> Result<()> {
424+
let boot_info = build_boot_info(attestation, false, "")
425+
.context("Failed to build KMS boot info from attestation")?;
426+
let response = cfg
427+
.auth_api
428+
.is_app_allowed(&boot_info, true)
429+
.await
430+
.context("Failed to call KMS auth check")?;
431+
if !response.is_allowed {
432+
bail!("Boot denied: {}", response.reason);
433+
}
434+
Ok(())
435+
}
436+
351437
async fn attest_keys(p256_pubkey: &[u8], k256_pubkey: &[u8]) -> Result<Vec<u8>> {
352438
let p256_hex = hex::encode(p256_pubkey);
353439
let k256_hex = hex::encode(k256_pubkey);

0 commit comments

Comments
 (0)