@@ -8,14 +8,16 @@ use std::sync::Arc;
88use std:: task:: { Context , Poll } ;
99
1010use anyhow:: { anyhow, bail, Context as _, Result } ;
11+ use dstack_guest_agent_rpc:: v0:: AppInfo ;
12+ use dstack_types:: AppCompose ;
1113use hyper:: body:: Incoming ;
1214use hyper:: server:: conn:: http1;
1315use hyper:: service:: service_fn;
1416use hyper:: { Request , Response , StatusCode } ;
1517use hyper_util:: rt:: tokio:: TokioIo ;
1618use proxy_protocol:: ProxyHeader ;
1719use rustls:: version:: { TLS12 , TLS13 } ;
18- use serde:: Serialize ;
20+ use serde:: { Deserialize , Serialize } ;
1921use tokio:: io:: { AsyncRead , AsyncWrite , AsyncWriteExt , ReadBuf } ;
2022use tokio:: net:: TcpStream ;
2123use tokio:: time:: timeout;
@@ -206,7 +208,7 @@ impl Proxy {
206208 "/app-info" => {
207209 let agent = crate :: dstack_agent ( ) . context ( "Failed to get dstack agent" ) ?;
208210 let app_info = agent. info ( ) . await . context ( "Failed to get app info" ) ?;
209- json_response ( & app_info)
211+ json_response ( & public_app_info ( app_info) )
210212 }
211213 "/acme-info" => {
212214 let acme_info = self . acme_info ( None ) . context ( "Failed to get acme info" ) ?;
@@ -618,6 +620,27 @@ impl AsyncWrite for MergedStream {
618620 }
619621}
620622
623+ /// The internal `Info` call returns the TCB info and VM config even when the
624+ /// app compose sets `public_tcbinfo = false`; the guest agent only withholds
625+ /// them on its external surface. `/app-info` is public, so withhold them here too.
626+ fn public_app_info ( mut info : AppInfo ) -> AppInfo {
627+ if !tcb_info_is_public ( & info. tcb_info ) {
628+ info. tcb_info . clear ( ) ;
629+ info. vm_config . clear ( ) ;
630+ }
631+ info
632+ }
633+
634+ fn tcb_info_is_public ( tcb_info : & str ) -> bool {
635+ #[ derive( Deserialize ) ]
636+ struct TcbInfo {
637+ app_compose : String ,
638+ }
639+ serde_json:: from_str :: < TcbInfo > ( tcb_info)
640+ . and_then ( |tcb| serde_json:: from_str :: < AppCompose > ( & tcb. app_compose ) )
641+ . is_ok_and ( |compose| compose. public_tcbinfo )
642+ }
643+
621644#[ cfg( test) ]
622645mod tests {
623646 use super :: * ;
@@ -659,4 +682,37 @@ mod tests {
659682 let ( remainder, _socket) = stream. into_socket_parts ( ) ;
660683 assert ! ( remainder. is_empty( ) , "got {remainder:?}" ) ;
661684 }
685+
686+ fn app_info_with_compose ( extra : serde_json:: Value ) -> AppInfo {
687+ let mut compose = serde_json:: json!( {
688+ "manifest_version" : "3" ,
689+ "name" : "gateway" ,
690+ "runner" : "docker-compose" ,
691+ } ) ;
692+ compose
693+ . as_object_mut ( )
694+ . expect ( "object" )
695+ . extend ( extra. as_object ( ) . expect ( "object" ) . clone ( ) ) ;
696+ AppInfo {
697+ tcb_info : serde_json:: json!( { "app_compose" : compose. to_string( ) } ) . to_string ( ) ,
698+ vm_config : "{\" cpu_count\" :2}" . into ( ) ,
699+ ..Default :: default ( )
700+ }
701+ }
702+
703+ #[ test]
704+ fn app_info_withholds_tcb_info_when_the_compose_opts_out ( ) {
705+ let info = public_app_info ( app_info_with_compose (
706+ serde_json:: json!( { "public_tcbinfo" : false } ) ,
707+ ) ) ;
708+ assert ! ( info. tcb_info. is_empty( ) ) ;
709+ assert ! ( info. vm_config. is_empty( ) ) ;
710+ }
711+
712+ #[ test]
713+ fn app_info_keeps_tcb_info_by_default ( ) {
714+ let info = public_app_info ( app_info_with_compose ( serde_json:: json!( { } ) ) ) ;
715+ assert ! ( !info. tcb_info. is_empty( ) ) ;
716+ assert ! ( !info. vm_config. is_empty( ) ) ;
717+ }
662718}
0 commit comments