Skip to content

Commit c83c667

Browse files
committed
fix(gateway): honor public_tcbinfo on the public app-info route
1 parent f5c2e6f commit c83c667

1 file changed

Lines changed: 58 additions & 2 deletions

File tree

‎dstack/gateway/src/proxy/tls_terminate.rs‎

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ use std::sync::Arc;
88
use std::task::{Context, Poll};
99

1010
use anyhow::{anyhow, bail, Context as _, Result};
11+
use dstack_guest_agent_rpc::v0::AppInfo;
12+
use dstack_types::AppCompose;
1113
use hyper::body::Incoming;
1214
use hyper::server::conn::http1;
1315
use hyper::service::service_fn;
1416
use hyper::{Request, Response, StatusCode};
1517
use hyper_util::rt::tokio::TokioIo;
1618
use proxy_protocol::ProxyHeader;
1719
use rustls::version::{TLS12, TLS13};
18-
use serde::Serialize;
20+
use serde::{Deserialize, Serialize};
1921
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf};
2022
use tokio::net::TcpStream;
2123
use 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)]
622645
mod 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

Comments
 (0)