Skip to content

Commit fcbdf9b

Browse files
authored
Merge pull request #412 from Dstack-TEE/gateway-wavekv
gateway: Add support for multiple nodes using the same domain
2 parents 48f2bb7 + a1bae39 commit fcbdf9b

66 files changed

Lines changed: 12689 additions & 1501 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 540 additions & 399 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ serde-duration = { path = "serde-duration" }
8686
dstack-mr = { path = "dstack-mr" }
8787
dstack-verifier = { path = "verifier", default-features = false }
8888
size-parser = { path = "size-parser" }
89+
wavekv = "1.0.0"
8990

9091
# Core dependencies
9192
anyhow = { version = "1.0.97", default-features = false }
93+
arc-swap = "1"
9294
errify = { version = "0.3.0", features = ["anyhow"] }
9395
or-panic = { version = "1.0", default-features = false }
9496
chrono = "0.4.40"
@@ -111,6 +113,7 @@ listenfd = "1.0"
111113
jemallocator = "0.5.4"
112114

113115
# Serialization/Parsing
116+
flate2 = "1.1"
114117
borsh = { version = "1.5.7", default-features = false, features = ["derive"] }
115118
bon = { version = "3.4.0", default-features = false }
116119
base64 = "0.22.1"
@@ -124,9 +127,9 @@ scale = { version = "3.7.4", package = "parity-scale-codec", features = [
124127
] }
125128
serde = { version = "1.0.228", features = ["derive"], default-features = false }
126129
serde-human-bytes = "0.1.2"
130+
rmp-serde = "1.3.1"
127131
serde_json = { version = "1.0.140", default-features = false }
128132
serde_ini = "0.2.0"
129-
rmp-serde = "1.3.1"
130133
toml = "0.8.20"
131134
toml_edit = { version = "0.22.24", features = ["serde"] }
132135
yasna = "0.5.2"
@@ -148,6 +151,11 @@ hyper-util = { version = "0.1.10", features = [
148151
"client-legacy",
149152
"http1",
150153
] }
154+
hyper-rustls = { version = "0.27", default-features = false, features = [
155+
"ring",
156+
"http1",
157+
"tls12",
158+
] }
151159
hyperlocal = "0.9.1"
152160
ipnet = { version = "2.11.0", features = ["serde"] }
153161
reqwest = { version = "0.12.14", default-features = false, features = [
@@ -237,7 +245,6 @@ yaml-rust2 = "0.10.4"
237245

238246
luks2 = "0.5.0"
239247
scopeguard = "1.2.0"
240-
flate2 = "1.1"
241248
tar = "0.4"
242249

243250
[profile.release]

REUSE.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,12 @@ SPDX-License-Identifier = "CC0-1.0"
186186
path = "guest-agent/fixtures/*"
187187
SPDX-FileCopyrightText = "NONE"
188188
SPDX-License-Identifier = "CC0-1.0"
189+
190+
[[annotations]]
191+
path = [
192+
"gateway/test-run/e2e/certs/*",
193+
"gateway/test-run/e2e/configs/*",
194+
"gateway/test-run/e2e/pebble-config.json",
195+
]
196+
SPDX-FileCopyrightText = "NONE"
197+
SPDX-License-Identifier = "CC0-1.0"

certbot/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ license.workspace = true
1212
[dependencies]
1313
anyhow.workspace = true
1414
bon.workspace = true
15+
bytes.workspace = true
1516
enum_dispatch.workspace = true
1617
fs-err.workspace = true
1718
hickory-resolver.workspace = true
19+
http.workspace = true
20+
http-body-util.workspace = true
1821
instant-acme.workspace = true
1922
path-absolutize.workspace = true
2023
rcgen.workspace = true

certbot/cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ async fn main() -> Result<()> {
164164
{
165165
use tracing_subscriber::{fmt, EnvFilter};
166166
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
167-
fmt().with_env_filter(filter).init();
167+
fmt().with_env_filter(filter).with_ansi(false).init();
168168
}
169169
rustls::crypto::ring::default_provider()
170170
.install_default()

certbot/src/acme_client.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use tracing::{debug, error, info};
2121
use x509_parser::prelude::{GeneralName, Pem};
2222

2323
use super::dns01_client::{Dns01Api, Dns01Client};
24+
use super::http_client::ReqwestHttpClient;
2425

2526
/// A AcmeClient instance.
2627
pub struct AcmeClient {
@@ -63,7 +64,9 @@ impl AcmeClient {
6364
dns_txt_ttl: u32,
6465
) -> Result<Self> {
6566
let credentials: Credentials = serde_json::from_str(encoded_credentials)?;
66-
let account = Account::from_credentials(credentials.credentials).await?;
67+
let http_client = Box::new(ReqwestHttpClient::new()?);
68+
let account =
69+
Account::from_credentials_and_http(credentials.credentials, http_client).await?;
6770
let credentials: Credentials = serde_json::from_str(encoded_credentials)?;
6871
Ok(Self {
6972
account,
@@ -81,14 +84,16 @@ impl AcmeClient {
8184
max_dns_wait: Duration,
8285
dns_txt_ttl: u32,
8386
) -> Result<Self> {
84-
let (account, credentials) = Account::create(
87+
let http_client = Box::new(ReqwestHttpClient::new()?);
88+
let (account, credentials) = Account::create_with_http(
8589
&NewAccount {
8690
contact: &[],
8791
terms_of_service_agreed: true,
8892
only_return_existing: false,
8993
},
9094
acme_url,
9195
None,
96+
http_client,
9297
)
9398
.await
9499
.with_context(|| format!("failed to create ACME account for {acme_url}"))?;

certbot/src/acme_client/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ async fn new_acme_client() -> Result<AcmeClient> {
1010
let dns01_client = Dns01Client::new_cloudflare(
1111
std::env::var("CLOUDFLARE_ZONE_ID").expect("CLOUDFLARE_ZONE_ID not set"),
1212
std::env::var("CLOUDFLARE_API_TOKEN").expect("CLOUDFLARE_API_TOKEN not set"),
13+
std::env::var("CLOUDFLARE_API_URL").ok(),
1314
);
1415
let credentials =
1516
std::env::var("LETSENCRYPT_CREDENTIAL").expect("LETSENCRYPT_CREDENTIAL not set");

certbot/src/bot.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct CertBotConfig {
2828
credentials_file: PathBuf,
2929
auto_create_account: bool,
3030
cf_api_token: String,
31+
cf_api_url: Option<String>,
3132
cert_file: PathBuf,
3233
key_file: PathBuf,
3334
cert_dir: PathBuf,
@@ -94,8 +95,12 @@ impl CertBot {
9495
.trim_start_matches("*.")
9596
.trim_end_matches('.')
9697
.to_string();
97-
let dns01_client =
98-
Dns01Client::new_cloudflare(config.cf_api_token.clone(), base_domain).await?;
98+
let dns01_client = Dns01Client::new_cloudflare(
99+
base_domain,
100+
config.cf_api_token.clone(),
101+
config.cf_api_url.clone(),
102+
)
103+
.await?;
99104
let acme_client = match fs::read_to_string(&config.credentials_file) {
100105
Ok(credentials) => {
101106
if acme_matches(&credentials, &config.acme_url) {

certbot/src/dns01_client.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,12 @@ pub enum Dns01Client {
7272
}
7373

7474
impl Dns01Client {
75-
pub async fn new_cloudflare(api_token: String, base_domain: String) -> Result<Self> {
76-
Ok(Self::Cloudflare(
77-
CloudflareClient::new(api_token, base_domain).await?,
78-
))
75+
pub async fn new_cloudflare(
76+
base_domain: String,
77+
api_token: String,
78+
api_url: Option<String>,
79+
) -> Result<Self> {
80+
let client = CloudflareClient::new(base_domain, api_token, api_url).await?;
81+
Ok(Self::Cloudflare(client))
7982
}
8083
}

certbot/src/dns01_client/cloudflare.rs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ use crate::dns01_client::Record;
1414

1515
use super::Dns01Api;
1616

17-
const CLOUDFLARE_API_URL: &str = "https://api.cloudflare.com/client/v4";
17+
const DEFAULT_CLOUDFLARE_API_URL: &str = "https://api.cloudflare.com/client/v4";
1818

1919
#[derive(Debug, Serialize, Deserialize)]
2020
pub struct CloudflareClient {
2121
zone_id: String,
2222
api_token: String,
23+
#[serde(default = "default_api_url")]
24+
api_url: String,
25+
}
26+
27+
fn default_api_url() -> String {
28+
DEFAULT_CLOUDFLARE_API_URL.to_string()
2329
}
2430

2531
#[derive(Deserialize)]
@@ -59,20 +65,29 @@ struct ZonesResultInfo {
5965
}
6066

6167
impl CloudflareClient {
62-
pub async fn new(api_token: String, base_domain: String) -> Result<Self> {
63-
let zone_id = Self::resolve_zone_id(&api_token, &base_domain).await?;
64-
Ok(Self { api_token, zone_id })
68+
pub async fn new(
69+
base_domain: String,
70+
api_token: String,
71+
api_url: Option<String>,
72+
) -> Result<Self> {
73+
let api_url = api_url.unwrap_or_else(|| DEFAULT_CLOUDFLARE_API_URL.to_string());
74+
let zone_id = Self::resolve_zone_id(&api_token, &base_domain, &api_url).await?;
75+
Ok(Self {
76+
zone_id,
77+
api_token,
78+
api_url,
79+
})
6580
}
6681

67-
async fn resolve_zone_id(api_token: &str, base_domain: &str) -> Result<String> {
82+
async fn resolve_zone_id(api_token: &str, base_domain: &str, api_url: &str) -> Result<String> {
6883
let base = base_domain
6984
.trim()
7085
.trim_start_matches("*.")
7186
.trim_end_matches('.')
7287
.to_lowercase();
7388

7489
let client = Client::new();
75-
let url = format!("{CLOUDFLARE_API_URL}/zones");
90+
let url = format!("{api_url}/zones");
7691

7792
let per_page = 50u32;
7893
let mut page = 1u32;
@@ -150,8 +165,7 @@ impl CloudflareClient {
150165

151166
async fn add_record(&self, record: &impl Serialize) -> Result<Response> {
152167
let client = Client::new();
153-
let url = format!("{CLOUDFLARE_API_URL}/zones/{}/dns_records", self.zone_id);
154-
168+
let url = format!("{}/zones/{}/dns_records", self.api_url, self.zone_id);
155169
let response = client
156170
.post(&url)
157171
.header("Authorization", format!("Bearer {}", self.api_token))
@@ -176,8 +190,8 @@ impl CloudflareClient {
176190
async fn remove_record_inner(&self, record_id: &str) -> Result<()> {
177191
let client = Client::new();
178192
let url = format!(
179-
"{CLOUDFLARE_API_URL}/zones/{zone_id}/dns_records/{record_id}",
180-
zone_id = self.zone_id
193+
"{}/zones/{}/dns_records/{}",
194+
self.api_url, self.zone_id, record_id
181195
);
182196

183197
debug!(url = %url, "cloudflare remove_record request");
@@ -201,7 +215,7 @@ impl CloudflareClient {
201215

202216
async fn get_records_inner(&self, domain: &str) -> Result<Vec<Record>> {
203217
let client = Client::new();
204-
let url = format!("{CLOUDFLARE_API_URL}/zones/{}/dns_records", self.zone_id);
218+
let url = format!("{}/zones/{}/dns_records", self.api_url, self.zone_id);
205219

206220
let per_page = 100u32;
207221
let mut records = Vec::new();
@@ -338,8 +352,9 @@ mod tests {
338352

339353
async fn create_client() -> CloudflareClient {
340354
CloudflareClient::new(
341-
std::env::var("CLOUDFLARE_API_TOKEN").expect("CLOUDFLARE_API_TOKEN not set"),
342355
std::env::var("TEST_DOMAIN").expect("TEST_DOMAIN not set"),
356+
std::env::var("CLOUDFLARE_API_TOKEN").expect("CLOUDFLARE_API_TOKEN not set"),
357+
std::env::var("CLOUDFLARE_API_URL").ok(),
343358
)
344359
.await
345360
.unwrap()

0 commit comments

Comments
 (0)