Skip to content

Commit e8fc392

Browse files
committed
gw: Better debug keys setting
1 parent c0fbbbe commit e8fc392

6 files changed

Lines changed: 177 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gateway/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,15 @@ flate2.workspace = true
6060
uuid = { workspace = true, features = ["v4"] }
6161
rmp-serde.workspace = true
6262
or-panic.workspace = true
63+
base64.workspace = true
6364

6465
[target.'cfg(unix)'.dependencies]
6566
nix = { workspace = true, features = ["resource"] }
6667

68+
[[bin]]
69+
name = "gen_debug_key"
70+
path = "src/gen_debug_key.rs"
71+
6772
[dev-dependencies]
6873
insta.workspace = true
6974
tempfile.workspace = true

gateway/gateway.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ address = "127.0.0.1:8011"
2828
[core.debug]
2929
insecure_enable_debug_rpc = false
3030
insecure_skip_attestation = false
31+
key_file = "debug_key.json"
3132
address = "127.0.0.1:8012"
3233

3334
[core.wg]

gateway/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ pub struct DebugConfig {
161161
pub insecure_enable_debug_rpc: bool,
162162
#[serde(default)]
163163
pub insecure_skip_attestation: bool,
164+
/// Path to pre-generated debug key data file (JSON format containing key, quote, event_log, and vm_config)
165+
#[serde(default)]
166+
pub key_file: String,
164167
}
165168

166169
#[derive(Debug, Clone, Deserialize)]

gateway/src/gen_debug_key.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Run with: cargo run --bin gen_debug_key -- <simulator_url>
2+
// Example: cargo run --bin gen_debug_key -- https://daee134c3b9f66aa2401c3b5ea64f1d34038f45d-3000.tdxlab.dstack.org:12004
3+
4+
use anyhow::{Context, Result};
5+
use base64::{engine::general_purpose::STANDARD, Engine as _};
6+
use dstack_guest_agent_rpc::{dstack_guest_client::DstackGuestClient, RawQuoteArgs};
7+
use http_client::prpc::PrpcClient;
8+
use ra_tls::attestation::QuoteContentType;
9+
use ra_tls::rcgen::KeyPair;
10+
use serde::{Deserialize, Serialize};
11+
12+
#[derive(Debug, Clone, Serialize, Deserialize)]
13+
struct DebugKeyData {
14+
/// Private key in PEM format
15+
key_pem: String,
16+
/// TDX quote in base64 format
17+
quote_base64: String,
18+
/// Event log in JSON string format
19+
event_log: String,
20+
/// VM config in JSON string format
21+
vm_config: String,
22+
}
23+
24+
#[tokio::main]
25+
async fn main() -> Result<()> {
26+
let args: Vec<String> = std::env::args().collect();
27+
if args.len() != 2 {
28+
eprintln!("Usage: {} <simulator_url>", args[0]);
29+
eprintln!("Example: {} https://daee134c3b9f66aa2401c3b5ea64f1d34038f45d-3000.tdxlab.dstack.org:12004", args[0]);
30+
std::process::exit(1);
31+
}
32+
let simulator_url = &args[1];
33+
34+
// Generate key pair
35+
let key = KeyPair::generate().context("Failed to generate key")?;
36+
let pubkey = key.public_key_der();
37+
let key_pem = key.serialize_pem();
38+
39+
// Calculate report_data
40+
let report_data = QuoteContentType::RaTlsCert.to_report_data(&pubkey);
41+
42+
// Get quote from simulator
43+
println!("Getting quote from simulator: {simulator_url}");
44+
let simulator_client = PrpcClient::new(simulator_url.to_string());
45+
let simulator_client = DstackGuestClient::new(simulator_client);
46+
let quote_response = simulator_client
47+
.get_quote(RawQuoteArgs {
48+
report_data: report_data.to_vec(),
49+
})
50+
.await
51+
.context("Failed to get quote from simulator")?;
52+
53+
// Create debug key data structure
54+
let debug_data = DebugKeyData {
55+
key_pem,
56+
quote_base64: STANDARD.encode(&quote_response.quote),
57+
event_log: quote_response.event_log,
58+
vm_config: quote_response.vm_config,
59+
};
60+
61+
// Write to single JSON file
62+
let json_content =
63+
serde_json::to_string_pretty(&debug_data).context("Failed to serialize debug key data")?;
64+
let output_file = "debug_key.json";
65+
fs_err::write(output_file, json_content).context("Failed to write debug key file")?;
66+
67+
println!("✓ Successfully generated debug key data:");
68+
println!(" - {output_file}");
69+
println!("\nYou can now configure this path in your gateway config:");
70+
println!("[core.debug]");
71+
println!("insecure_skip_attestation = true");
72+
println!(
73+
"key_file = \"{}\"",
74+
fs_err::canonicalize(&output_file)
75+
.unwrap_or_default()
76+
.display()
77+
);
78+
79+
Ok(())
80+
}

gateway/src/main.rs

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
// SPDX-FileCopyrightText: © 2024-2025 Phala Network <dstack@phala.network>
1+
// SPDX-FileCopyrightText: 2024-2025 Phala Network dstack@phala.network
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

55
use anyhow::{anyhow, Context, Result};
6+
use base64::{engine::general_purpose::STANDARD, Engine as _};
67
use clap::Parser;
78
use config::{Config, TlsConfig};
89
use dstack_guest_agent_rpc::{dstack_guest_client::DstackGuestClient, GetTlsKeyArgs};
10+
use dstack_kms_rpc::SignCertRequest;
911
use http_client::prpc::PrpcClient;
1012
use ra_rpc::{client::RaClient, prpc_routes as prpc, rocket_helper::QuoteVerifier};
13+
use ra_tls::cert::{CertConfigV2, CertSigningRequestV2, Csr};
14+
use ra_tls::rcgen::KeyPair;
1115
use rocket::{
1216
fairing::AdHoc,
1317
figment::{providers::Serialized, Figment},
1418
};
19+
use serde::{Deserialize, Serialize};
1520
use tracing::info;
1621

1722
use admin_service::AdminRpcHandler;
@@ -30,6 +35,18 @@ mod models;
3035
mod proxy;
3136
mod web_routes;
3237

38+
#[derive(Debug, Clone, Serialize, Deserialize)]
39+
struct DebugKeyData {
40+
/// Private key in PEM format
41+
key_pem: String,
42+
/// TDX quote in base64 format
43+
quote_base64: String,
44+
/// Event log in JSON string format
45+
event_log: String,
46+
/// VM config in JSON string format
47+
vm_config: String,
48+
}
49+
3350
#[global_allocator]
3451
static ALLOCATOR: jemallocator::Jemalloc = jemallocator::Jemalloc;
3552

@@ -126,29 +143,78 @@ async fn gen_debug_certs(
126143
info!("KMS URL is empty, skipping cert generation");
127144
return Ok(());
128145
}
146+
147+
// Check if debug key file is configured
148+
if config.debug.key_file.is_empty() {
149+
info!("Debug key file not configured, skipping cert generation");
150+
return Ok(());
151+
}
152+
153+
// Load pre-generated key pair and quote data from JSON file
154+
info!("Loading debug key data from: {}", config.debug.key_file);
155+
let ctx = "Failed to read debug key, run `cargo run --bin gen_debug_key -- <simulator_url>` to generate it";
156+
let json_content = fs_err::read_to_string(&config.debug.key_file).context(ctx)?;
157+
let debug_data: DebugKeyData =
158+
serde_json::from_str(&json_content).context("Failed to parse debug key JSON")?;
159+
160+
let key_pem = debug_data.key_pem;
161+
let quote_bin = STANDARD
162+
.decode(&debug_data.quote_base64)
163+
.context("Failed to decode quote from base64")?;
164+
let event_log_json = debug_data.event_log;
165+
let vm_config_json = debug_data.vm_config;
166+
167+
// Parse key pair
168+
let key = KeyPair::from_pem(&key_pem).context("Failed to parse debug key")?;
169+
let pubkey = key.public_key_der();
170+
171+
// Build CSR with attestation from debug quote
172+
let attestation =
173+
ra_tls::attestation::Attestation::from_tdx_quote(quote_bin, event_log_json.as_bytes())
174+
.context("Failed to create attestation from debug quote")?
175+
.into_versioned();
176+
177+
let csr = CertSigningRequestV2 {
178+
confirm: "please sign cert:".to_string(),
179+
pubkey,
180+
config: CertConfigV2 {
181+
org_name: None,
182+
subject: "dstack-gateway".to_string(),
183+
subject_alt_names: alt_names,
184+
usage_server_auth: true,
185+
usage_client_auth: true,
186+
ext_quote: true,
187+
not_before: None,
188+
not_after: None,
189+
},
190+
attestation,
191+
};
192+
let signature = csr.signed_by(&key).context("Failed to sign CSR")?;
193+
194+
// Send CSR to KMS for signing
129195
let kms_url = format!("{kms_url}/prpc");
130-
info!("Getting temp CA cert from {kms_url}");
131-
let client = RaClient::new(kms_url, true).context("Failed to create kms client")?;
132-
let client = dstack_kms_rpc::kms_client::KmsClient::new(client);
133-
let temp_ca_response = client.get_temp_ca_cert().await?;
134-
let ca_cert = temp_ca_response.temp_ca_cert.clone();
135-
let temp_ca =
136-
ra_tls::cert::CaCert::new(temp_ca_response.temp_ca_cert, temp_ca_response.temp_ca_key)
137-
.context("Failed to create temp CA")?;
138-
let key = ra_tls::rcgen::KeyPair::generate().context("Failed to generate key")?;
139-
let cert_req = ra_tls::cert::CertRequest::builder()
140-
.key(&key)
141-
.subject("dstack-gateway")
142-
.alt_names(&alt_names)
143-
.usage_server_auth(true)
144-
.usage_client_auth(true)
145-
.build();
146-
let cert = temp_ca
147-
.sign(cert_req)
148-
.context("Failed to sign rpc cert with temp CA")?;
196+
info!("Sending CSR to KMS for signing: {kms_url}");
197+
let kms_client = RaClient::new(kms_url, true).context("Failed to create kms client")?;
198+
let kms_client = dstack_kms_rpc::kms_client::KmsClient::new(kms_client);
199+
let sign_response = kms_client
200+
.sign_cert(SignCertRequest {
201+
api_version: 2,
202+
csr: csr.to_vec(),
203+
signature,
204+
vm_config: vm_config_json.to_string(),
205+
})
206+
.await
207+
.context("Failed to sign certificate via KMS")?;
208+
209+
let ca_cert = sign_response
210+
.certificate_chain
211+
.last()
212+
.context("Empty certificate chain")?
213+
.to_string();
214+
let certs = sign_response.certificate_chain.join("\n");
149215

150216
write_cert(&tls_config.mutual.ca_certs, &ca_cert)?;
151-
write_cert(&tls_config.certs, &cert.pem())?;
217+
write_cert(&tls_config.certs, &certs)?;
152218
write_cert(&tls_config.key, &key.serialize_pem())?;
153219
Ok(())
154220
}

0 commit comments

Comments
 (0)