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 } ;
68use dstack_guest_agent_rpc:: {
79 dstack_guest_client:: DstackGuestClient , AttestResponse , RawQuoteArgs ,
810} ;
@@ -15,15 +17,18 @@ use dstack_kms_rpc::{
1517use fs_err as fs;
1618use http_client:: prpc:: PrpcClient ;
1719use k256:: ecdsa:: SigningKey ;
18- use ra_rpc:: { client:: RaClient , CallContext , RpcCall } ;
20+ use ra_rpc:: {
21+ client:: { CertInfo , RaClient , RaClientConfig } ,
22+ CallContext , RpcCall ,
23+ } ;
1924use ra_tls:: {
20- attestation:: { QuoteContentType , VersionedAttestation } ,
25+ attestation:: { QuoteContentType , VerifiedAttestation , VersionedAttestation } ,
2126 cert:: { CaCert , CertRequest } ,
2227 rcgen:: { Certificate , KeyPair , PKCS_ECDSA_P256_SHA256 } ,
2328} ;
2429use 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 ) ]
2934pub struct OnboardState {
@@ -53,6 +58,11 @@ impl RpcCall<OnboardState> for OnboardHandler {
5358impl 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
330376pub ( 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+
351437async 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