dstack_sdk/
tappd_client.rs1use crate::dstack_client::BaseClient;
7use anyhow::{bail, Result};
8use hex::encode as hex_encode;
9use http_client_unix_domain_socket::{ClientUnix, Method};
10use reqwest::Client;
11use serde::{de::DeserializeOwned, Deserialize, Serialize};
12use serde_json::{json, Value};
13use std::env;
14
15pub use dstack_sdk_types::tappd::*;
16
17fn get_tappd_endpoint(endpoint: Option<&str>) -> String {
18 if let Some(e) = endpoint {
19 return e.to_string();
20 }
21 if let Ok(sim_endpoint) = env::var("TAPPD_SIMULATOR_ENDPOINT") {
22 return sim_endpoint;
23 }
24 const SOCKET_PATHS: &[&str] = &[
26 "/var/run/tappd.sock",
27 "/run/tappd.sock",
28 "/var/run/dstack/tappd.sock",
29 "/run/dstack/tappd.sock",
30 ];
31 for path in SOCKET_PATHS {
32 if std::path::Path::new(path).exists() {
33 return path.to_string();
34 }
35 }
36 SOCKET_PATHS[0].to_string()
37}
38
39#[derive(Debug)]
40pub enum TappdClientKind {
41 Http,
42 Unix,
43}
44
45pub struct TappdClient {
47 base_url: String,
49 endpoint: String,
51 client: TappdClientKind,
53}
54
55impl BaseClient for TappdClient {}
56
57impl TappdClient {
58 pub fn new(endpoint: Option<&str>) -> Self {
59 let endpoint = get_tappd_endpoint(endpoint);
60 let (base_url, client) = match endpoint {
61 ref e if e.starts_with("http://") || e.starts_with("https://") => {
62 (e.to_string(), TappdClientKind::Http)
63 }
64 _ => ("http://localhost".to_string(), TappdClientKind::Unix),
65 };
66
67 TappdClient {
68 base_url,
69 endpoint,
70 client,
71 }
72 }
73
74 async fn send_rpc_request<S: Serialize, D: DeserializeOwned>(
75 &self,
76 path: &str,
77 payload: &S,
78 ) -> anyhow::Result<D> {
79 match &self.client {
80 TappdClientKind::Http => {
81 let client = Client::new();
82 let url = format!(
83 "{}/{}",
84 self.base_url.trim_end_matches('/'),
85 path.trim_start_matches('/')
86 );
87 let res = client
88 .post(&url)
89 .json(payload)
90 .header("Content-Type", "application/json")
91 .send()
92 .await?
93 .error_for_status()?;
94 Ok(res.json().await?)
95 }
96 TappdClientKind::Unix => {
97 let mut unix_client = ClientUnix::try_new(&self.endpoint).await?;
98 let res = unix_client
99 .send_request_json::<_, _, Value>(
100 path,
101 Method::POST,
102 &[("Content-Type", "application/json")],
103 Some(&payload),
104 )
105 .await?;
106 Ok(res.1)
107 }
108 }
109 }
110
111 pub async fn derive_key(&self, path: &str) -> Result<DeriveKeyResponse> {
113 self.derive_key_with_subject_and_alt_names(path, Some(path), None)
114 .await
115 }
116
117 pub async fn derive_key_with_subject(
119 &self,
120 path: &str,
121 subject: &str,
122 ) -> Result<DeriveKeyResponse> {
123 self.derive_key_with_subject_and_alt_names(path, Some(subject), None)
124 .await
125 }
126
127 pub async fn derive_key_with_subject_and_alt_names(
129 &self,
130 path: &str,
131 subject: Option<&str>,
132 alt_names: Option<Vec<String>>,
133 ) -> Result<DeriveKeyResponse> {
134 let subject = subject.unwrap_or(path);
135
136 let mut payload = json!({
137 "path": path,
138 "subject": subject,
139 });
140
141 if let Some(alt_names) = alt_names {
142 if !alt_names.is_empty() {
143 payload["alt_names"] = json!(alt_names);
144 }
145 }
146
147 let response = self
148 .send_rpc_request("/prpc/Tappd.DeriveKey", &payload)
149 .await?;
150 Ok(response)
151 }
152
153 pub async fn get_quote(&self, report_data: Vec<u8>) -> Result<TdxQuoteResponse> {
155 if report_data.len() != 64 {
156 bail!("Report data must be exactly 64 bytes for raw quote");
157 }
158
159 let payload = json!({
160 "report_data": hex_encode(report_data),
161 });
162
163 let response = self
164 .send_rpc_request("/prpc/Tappd.RawQuote", &payload)
165 .await?;
166 Ok(response)
167 }
168
169 pub async fn info(&self) -> Result<TappdInfoResponse> {
171 #[derive(Deserialize)]
172 struct RawInfoResponse {
173 app_id: String,
174 instance_id: String,
175 app_cert: String,
176 tcb_info: String,
177 app_name: String,
178 }
179
180 let raw_response: RawInfoResponse = self
181 .send_rpc_request("/prpc/Tappd.Info", &json!({}))
182 .await?;
183
184 let tcb_info: TappdTcbInfo = serde_json::from_str(&raw_response.tcb_info)?;
185
186 Ok(TappdInfoResponse {
187 app_id: raw_response.app_id,
188 instance_id: raw_response.instance_id,
189 app_cert: raw_response.app_cert,
190 tcb_info,
191 app_name: raw_response.app_name,
192 })
193 }
194}