Skip to content

Commit ed0a7a0

Browse files
authored
Merge pull request #1400 from Dstack-TEE/fix/gateway-app-info-public-tcbinfo
fix(gateway): honor public_tcbinfo on the public app-info route
2 parents a354f9a + d543872 commit ed0a7a0

1 file changed

Lines changed: 54 additions & 3 deletions

File tree

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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44

55
use std::io;
66
use std::pin::Pin;
7-
use std::sync::Arc;
7+
use std::sync::{Arc, OnceLock};
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,29 @@ 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+
/// The choice is fixed for the CVM's lifetime, so it is decided once.
627+
fn public_app_info(mut info: AppInfo) -> AppInfo {
628+
static TCB_INFO_PUBLIC: OnceLock<bool> = OnceLock::new();
629+
if !*TCB_INFO_PUBLIC.get_or_init(|| tcb_info_is_public(&info.tcb_info)) {
630+
info.tcb_info.clear();
631+
info.vm_config.clear();
632+
}
633+
info
634+
}
635+
636+
fn tcb_info_is_public(tcb_info: &str) -> bool {
637+
#[derive(Deserialize)]
638+
struct TcbInfo {
639+
app_compose: String,
640+
}
641+
serde_json::from_str::<TcbInfo>(tcb_info)
642+
.and_then(|tcb| serde_json::from_str::<AppCompose>(&tcb.app_compose))
643+
.is_ok_and(|compose| compose.public_tcbinfo)
644+
}
645+
621646
#[cfg(test)]
622647
mod tests {
623648
use super::*;
@@ -659,4 +684,30 @@ mod tests {
659684
let (remainder, _socket) = stream.into_socket_parts();
660685
assert!(remainder.is_empty(), "got {remainder:?}");
661686
}
687+
688+
fn tcb_info_with_compose(extra: serde_json::Value) -> String {
689+
let mut compose = serde_json::json!({
690+
"manifest_version": "3",
691+
"name": "gateway",
692+
"runner": "docker-compose",
693+
});
694+
compose
695+
.as_object_mut()
696+
.expect("object")
697+
.extend(extra.as_object().expect("object").clone());
698+
serde_json::json!({ "app_compose": compose.to_string() }).to_string()
699+
}
700+
701+
#[test]
702+
fn tcb_info_is_private_when_the_compose_opts_out() {
703+
let tcb_info = tcb_info_with_compose(serde_json::json!({ "public_tcbinfo": false }));
704+
assert!(!tcb_info_is_public(&tcb_info));
705+
}
706+
707+
#[test]
708+
fn tcb_info_is_public_by_default() {
709+
assert!(tcb_info_is_public(&tcb_info_with_compose(
710+
serde_json::json!({})
711+
)));
712+
}
662713
}

0 commit comments

Comments
 (0)