Skip to content

Commit d529022

Browse files
committed
TLS passthrough will select known app_id from TXT record w multiple entries
1 parent 282eeb2 commit d529022

1 file changed

Lines changed: 79 additions & 17 deletions

File tree

gateway/src/proxy/tls_passthough.rs

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5+
use std::collections::{BTreeMap, BTreeSet};
56
use std::fmt::Debug;
67
use std::sync::atomic::Ordering;
78

@@ -39,8 +40,24 @@ impl AppAddress {
3940
}
4041
}
4142

43+
/// find app by app id in current memory box
44+
fn select_app_address(items: &[Box<[u8]>], known_apps: &BTreeMap<String, BTreeSet<String>>) -> Result<AppAddress> {
45+
let mut fallback = None;
46+
for data in items {
47+
if let Ok(addr) = AppAddress::parse(data) {
48+
if known_apps.contains_key(&addr.app_id) {
49+
return Ok(addr);
50+
}
51+
if fallback.is_none() {
52+
fallback = Some(addr);
53+
}
54+
}
55+
}
56+
fallback.context("no app address found in txt record")
57+
}
58+
4259
/// resolve app address by sni
43-
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<AppAddress> {
60+
async fn resolve_app_address(prefix: &str, sni: &str, compat: bool, state: &Proxy) -> Result<AppAddress> {
4461
let txt_domain = format!("{prefix}.{sni}");
4562
let resolver = hickory_resolver::AsyncResolver::tokio_from_system_conf()
4663
.context("failed to create dns resolver")?;
@@ -58,17 +75,16 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
5875
let Some(txt_record) = lookup.iter().next() else {
5976
continue;
6077
};
61-
let Some(data) = txt_record.txt_data().first() else {
62-
continue;
63-
};
64-
return AppAddress::parse(data)
65-
.with_context(|| format!("failed to parse app address for {sni}"));
78+
let locked = state.lock();
79+
if let Ok(addr) = select_app_address(txt_record.txt_data(), &locked.state.apps) {
80+
return Ok(addr);
81+
}
6682
}
6783
} else if let Ok(lookup) = resolver.txt_lookup(txt_domain).await {
6884
if let Some(txt_record) = lookup.iter().next() {
69-
if let Some(data) = txt_record.txt_data().first() {
70-
return AppAddress::parse(data)
71-
.with_context(|| format!("failed to parse app address for {sni}"));
85+
let locked = state.lock();
86+
if let Ok(addr) = select_app_address(txt_record.txt_data(), &locked.state.apps) {
87+
return Ok(addr);
7288
}
7389
}
7490
}
@@ -86,13 +102,11 @@ async fn resolve_app_address(prefix: &str, sni: &str, compat: bool) -> Result<Ap
86102
.iter()
87103
.next()
88104
.with_context(|| format!("no txt record found for {sni} via {wildcard_domain}"))?;
89-
let data = txt_record
90-
.txt_data()
91-
.first()
92-
.with_context(|| format!("no data in txt record for {sni} via {wildcard_domain}"))?;
93-
return AppAddress::parse(data).with_context(|| {
94-
format!("failed to parse app address for {sni} via {wildcard_domain}")
95-
});
105+
let locked = state.lock();
106+
return select_app_address(txt_record.txt_data(), &locked.state.apps)
107+
.with_context(|| {
108+
format!("failed to parse app address for {sni} via {wildcard_domain}")
109+
});
96110
}
97111

98112
anyhow::bail!("failed to resolve app address for {sni}");
@@ -108,7 +122,7 @@ pub(crate) async fn proxy_with_sni(
108122
let ns_prefix = &state.config.proxy.app_address_ns_prefix;
109123
let compat = state.config.proxy.app_address_ns_compat;
110124
let dns_timeout = state.config.proxy.timeouts.dns_resolve;
111-
let addr = timeout(dns_timeout, resolve_app_address(ns_prefix, sni, compat))
125+
let addr = timeout(dns_timeout, resolve_app_address(ns_prefix, sni, compat, &state))
112126
.await
113127
.with_context(|| format!("DNS TXT resolve timeout for {sni}"))?
114128
.with_context(|| format!("failed to resolve app address for {sni}"))?;
@@ -218,17 +232,65 @@ pub(crate) async fn proxy_to_app(
218232
#[cfg(test)]
219233
mod tests {
220234
use super::*;
235+
use crate::{
236+
config::{load_config_figment, Config, MutualConfig, TlsConfig},
237+
main_service::ProxyOptions,
238+
};
239+
use tempfile::TempDir;
240+
241+
fn boxed(s: &[u8]) -> Box<[u8]> {
242+
s.to_vec().into_boxed_slice()
243+
}
244+
245+
async fn create_test_proxy() -> (Proxy, TempDir) {
246+
let figment = load_config_figment(None);
247+
let mut config = figment.focus("core").extract::<Config>().unwrap();
248+
let temp_dir = TempDir::new().expect("failed to create temp dir");
249+
config.sync.data_dir = temp_dir.path().to_string_lossy().to_string();
250+
let proxy = Proxy::new(ProxyOptions {
251+
config,
252+
my_app_id: None,
253+
tls_config: TlsConfig {
254+
certs: "".to_string(),
255+
key: "".to_string(),
256+
mutual: MutualConfig { ca_certs: "".to_string() },
257+
},
258+
})
259+
.await
260+
.expect("failed to create proxy");
261+
(proxy, temp_dir)
262+
}
221263

222264
#[tokio::test]
223265
async fn test_resolve_app_address() {
266+
let (state, _dir) = create_test_proxy().await;
224267
let app_addr = resolve_app_address(
225268
"_dstack-app-address",
226269
"3327603e03f5bd1f830812ca4a789277fc31f577.app.dstack.org",
227270
false,
271+
&state,
228272
)
229273
.await
230274
.unwrap();
231275
assert_eq!(app_addr.app_id, "3327603e03f5bd1f830812ca4a789277fc31f577");
232276
assert_eq!(app_addr.port, 8090);
233277
}
278+
279+
#[test]
280+
fn test_select_app_address_prefers_local() {
281+
let items = vec![boxed(b"aaaaaa:443"), boxed(b"bbbbbb:8080")];
282+
let mut apps = BTreeMap::new();
283+
apps.insert("bbbbbb".to_string(), BTreeSet::new());
284+
let addr = select_app_address(&items, &apps).unwrap();
285+
assert_eq!(addr.app_id, "bbbbbb");
286+
assert_eq!(addr.port, 8080);
287+
}
288+
289+
#[test]
290+
fn test_select_app_address_fallback_when_none_local() {
291+
let items = vec![boxed(b"aaaaaa:443"), boxed(b"bbbbbb:8080")];
292+
let addr = select_app_address(&items, &BTreeMap::new()).unwrap();
293+
assert_eq!(addr.app_id, "aaaaaa");
294+
assert_eq!(addr.port, 443);
295+
}
234296
}

0 commit comments

Comments
 (0)