1mod codec;
20mod dyncfgs;
21
22use std::collections::BTreeMap;
23use std::net::{IpAddr, SocketAddr};
24use std::path::PathBuf;
25use std::pin::Pin;
26use std::sync::Arc;
27use std::time::{Duration, Instant};
28
29use anyhow::Context;
30use axum::response::IntoResponse;
31use axum::{Router, routing};
32use bytes::BytesMut;
33use futures::TryFutureExt;
34use futures::stream::BoxStream;
35use hickory_resolver::config::LookupIpStrategy;
36use hickory_resolver::lookup_ip::LookupIp;
37use hickory_resolver::net::runtime::TokioRuntimeProvider;
38use hickory_resolver::proto::rr::{RData, RecordType};
39use hickory_resolver::system_conf::read_system_conf;
40use hickory_resolver::{Resolver, TokioResolver};
41use hyper::StatusCode;
42use hyper_util::rt::TokioIo;
43use launchdarkly_server_sdk as ld;
44use mz_build_info::{BuildInfo, build_info};
45use mz_dyncfg::ConfigSet;
46use mz_frontegg_auth::Authenticator as FronteggAuthentication;
47use mz_ore::cast::CastFrom;
48use mz_ore::id_gen::conn_id_org_uuid;
49use mz_ore::metrics::{ComputedGauge, IntCounter, IntGauge, MetricsRegistry};
50use mz_ore::netio::AsyncReady;
51use mz_ore::now::{NowFn, SYSTEM_TIME, epoch_to_uuid_v7};
52use mz_ore::task::{JoinSetExt, spawn};
53use mz_ore::tracing::TracingHandle;
54use mz_ore::{metric, netio};
55use mz_pgwire_common::{
56 ACCEPT_SSL_ENCRYPTION, CONN_UUID_KEY, Conn, ErrorResponse, FrontendMessage,
57 FrontendStartupMessage, MZ_FORWARDED_FOR_KEY, REJECT_ENCRYPTION, VERSION_3, decode_startup,
58};
59use mz_server_core::{
60 Connection, ConnectionStream, ListenerHandle, ReloadTrigger, ReloadingSslContext,
61 ReloadingTlsConfig, ServeConfig, ServeDyncfg, TlsCertConfig, TlsMode, listen,
62};
63use openssl::ssl::{NameType, Ssl, SslConnector, SslMethod, SslVerifyMode};
64use prometheus::{IntCounterVec, IntGaugeVec};
65use proxy_header::{ProxiedAddress, ProxyHeader};
66use semver::Version;
67use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt};
68use tokio::net::TcpStream;
69use tokio::sync::oneshot;
70use tokio::task::JoinSet;
71use tokio_metrics::TaskMetrics;
72use tokio_openssl::SslStream;
73use tokio_postgres::error::SqlState;
74use tower::Service;
75use tracing::{debug, error, warn};
76use uuid::Uuid;
77
78use crate::codec::{BackendMessage, FramedConn};
79use crate::dyncfgs::{
80 INJECT_PROXY_PROTOCOL_HEADER_HTTP, SIGTERM_CONNECTION_WAIT, SIGTERM_LISTEN_WAIT,
81 has_tracing_config_update, tracing_config,
82};
83
84pub const BUILD_INFO: BuildInfo = build_info!();
86
87pub struct BalancerConfig {
88 build_version: Version,
90 internal_http_listen_addr: SocketAddr,
92 pgwire_listen_addr: SocketAddr,
94 https_listen_addr: SocketAddr,
96 cancellation_resolver: CancellationResolver,
98 resolver: BalancerResolver,
100 https_sni_addr_template: String,
101 tls: Option<TlsCertConfig>,
102 internal_tls: bool,
103 metrics_registry: MetricsRegistry,
104 reload_certs: BoxStream<'static, Option<oneshot::Sender<Result<(), anyhow::Error>>>>,
105 launchdarkly_sdk_key: Option<String>,
106 config_sync_file_path: Option<PathBuf>,
107 config_sync_timeout: Duration,
108 config_sync_loop_interval: Option<Duration>,
109 cloud_provider: Option<String>,
110 cloud_provider_region: Option<String>,
111 tracing_handle: TracingHandle,
112 default_configs: Vec<(String, String)>,
113}
114
115impl BalancerConfig {
116 pub fn new(
117 build_info: &BuildInfo,
118 internal_http_listen_addr: SocketAddr,
119 pgwire_listen_addr: SocketAddr,
120 https_listen_addr: SocketAddr,
121 cancellation_resolver: CancellationResolver,
122 resolver: BalancerResolver,
123 https_sni_addr_template: String,
124 tls: Option<TlsCertConfig>,
125 internal_tls: bool,
126 metrics_registry: MetricsRegistry,
127 reload_certs: ReloadTrigger,
128 launchdarkly_sdk_key: Option<String>,
129 config_sync_file: Option<PathBuf>,
130 config_sync_timeout: Duration,
131 config_sync_loop_interval: Option<Duration>,
132 cloud_provider: Option<String>,
133 cloud_provider_region: Option<String>,
134 tracing_handle: TracingHandle,
135 default_configs: Vec<(String, String)>,
136 ) -> Self {
137 Self {
138 build_version: build_info.semver_version(),
139 internal_http_listen_addr,
140 pgwire_listen_addr,
141 https_listen_addr,
142 cancellation_resolver,
143 resolver,
144 https_sni_addr_template,
145 tls,
146 internal_tls,
147 metrics_registry,
148 reload_certs,
149 launchdarkly_sdk_key,
150 config_sync_file_path: config_sync_file,
151 config_sync_timeout,
152 config_sync_loop_interval,
153 cloud_provider,
154 cloud_provider_region,
155 tracing_handle,
156 default_configs,
157 }
158 }
159}
160
161#[derive(Debug)]
163pub struct BalancerMetrics {
164 _uptime: ComputedGauge,
165}
166
167impl BalancerMetrics {
168 pub fn new(cfg: &BalancerConfig) -> Self {
170 let start = Instant::now();
171 let uptime = cfg.metrics_registry.register_computed_gauge(
172 metric!(
173 name: "mz_balancer_metadata_seconds",
174 help: "server uptime, labels are build metadata",
175 const_labels: {
176 "version" => cfg.build_version,
177 "build_type" => if cfg!(release) { "release" } else { "debug" }
178 },
179 ),
180 move || start.elapsed().as_secs_f64(),
181 );
182 BalancerMetrics { _uptime: uptime }
183 }
184}
185
186pub struct BalancerService {
187 cfg: BalancerConfig,
188 pub pgwire: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
189 pub https: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
190 pub internal_http: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
191 _metrics: BalancerMetrics,
192 configs: ConfigSet,
193}
194
195impl BalancerService {
196 pub async fn new(cfg: BalancerConfig) -> Result<Self, anyhow::Error> {
197 let pgwire = listen(&cfg.pgwire_listen_addr).await?;
198 let https = listen(&cfg.https_listen_addr).await?;
199 let internal_http = listen(&cfg.internal_http_listen_addr).await?;
200 let metrics = BalancerMetrics::new(&cfg);
201 let mut configs = ConfigSet::default();
202 configs = dyncfgs::all_dyncfgs(configs);
203 dyncfgs::set_defaults(&configs, cfg.default_configs.clone())?;
204 let tracing_handle = cfg.tracing_handle.clone();
205 match (
207 cfg.launchdarkly_sdk_key.as_deref(),
208 cfg.config_sync_file_path.as_deref(),
209 ) {
210 (Some(key), None) => {
211 let _ = mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
212 configs.clone(),
213 &BUILD_INFO,
214 |builder| {
215 let region = cfg
216 .cloud_provider_region
217 .clone()
218 .unwrap_or_else(|| String::from("unknown"));
219 if let Some(provider) = cfg.cloud_provider.clone() {
220 builder.add_context(
221 ld::ContextBuilder::new(format!(
222 "{}/{}/{}",
223 provider, region, cfg.build_version
224 ))
225 .kind("balancer")
226 .set_string("provider", provider)
227 .set_string("region", region)
228 .set_string("version", cfg.build_version.to_string())
229 .build()
230 .map_err(|e| anyhow::anyhow!(e))?,
231 );
232 } else {
233 builder.add_context(
234 ld::ContextBuilder::new(format!(
235 "{}/{}/{}",
236 "unknown", region, cfg.build_version
237 ))
238 .anonymous(true) .kind("balancer")
240 .set_string("provider", "unknown")
241 .set_string("region", region)
242 .set_string("version", cfg.build_version.to_string())
243 .build()
244 .map_err(|e| anyhow::anyhow!(e))?,
245 );
246 }
247 Ok(())
248 },
249 Some(key),
250 cfg.config_sync_timeout,
251 cfg.config_sync_loop_interval,
252 move |updates, configs| {
253 if has_tracing_config_update(updates) {
254 match tracing_config(configs) {
255 Ok(parameters) => parameters.apply(&tracing_handle),
256 Err(err) => warn!("unable to update tracing: {err}"),
257 }
258 }
259 },
260 )
261 .await
262 .inspect_err(|e| warn!("LaunchDarkly sync error: {e}"));
263 }
264 (None, Some(path)) => {
265 let _ = mz_dyncfg_file::sync_file_to_configset(
266 configs.clone(),
267 path,
268 cfg.config_sync_timeout,
269 cfg.config_sync_loop_interval,
270 move |updates, configs| {
271 if has_tracing_config_update(updates) {
272 match tracing_config(configs) {
273 Ok(parameters) => parameters.apply(&tracing_handle),
274 Err(err) => warn!("unable to update tracing: {err}"),
275 }
276 }
277 },
278 )
279 .await
280 .inspect_err(|e| warn!("File config sync error: {e}"));
286 }
287 (Some(_), Some(_)) => panic!(
288 "must provide either config_sync_file_path or launchdarkly_sdk_key for config syncing",
289 ),
290 (None, None) => {}
291 };
292 Ok(Self {
293 cfg,
294 pgwire,
295 https,
296 internal_http,
297 _metrics: metrics,
298 configs,
299 })
300 }
301
302 pub async fn serve(self) -> Result<(), anyhow::Error> {
303 let (pgwire_tls, https_tls) = match &self.cfg.tls {
304 Some(tls) => {
305 let context = tls.reloading_context(self.cfg.reload_certs)?;
306 (
307 Some(ReloadingTlsConfig {
308 context: context.clone(),
309 mode: TlsMode::Require,
310 }),
311 Some(context),
312 )
313 }
314 None => (None, None),
315 };
316
317 let metrics = ServerMetricsConfig::register_into(&self.cfg.metrics_registry);
318
319 let mut set = JoinSet::new();
320 let mut server_handles = Vec::new();
321 let pgwire_addr = self.pgwire.0.local_addr();
322 let https_addr = self.https.0.local_addr();
323 let internal_http_addr = self.internal_http.0.local_addr();
324
325 let shared_dns = match self.cfg.resolver.shared_dns() {
330 Some(dns) => dns,
331 None => Arc::new(TenantDnsResolver::new()?),
332 };
333
334 {
335 let pgwire = PgwireBalancer {
336 resolver: Arc::new(self.cfg.resolver),
337 cancellation_resolver: Arc::new(self.cfg.cancellation_resolver),
338 tls: pgwire_tls,
339 internal_tls: self.cfg.internal_tls,
340 metrics: ServerMetrics::new(metrics.clone(), "pgwire"),
341 now: SYSTEM_TIME.clone(),
342 };
343 let (handle, stream) = self.pgwire;
344 server_handles.push(handle);
345 set.spawn_named(|| "pgwire_stream", {
346 let config_set = self.configs.clone();
347 async move {
348 mz_server_core::serve(ServeConfig {
349 server: pgwire,
350 conns: stream,
351 dyncfg: Some(ServeDyncfg {
352 config_set,
353 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
354 }),
355 })
356 .await;
357 warn!("pgwire server exited");
358 }
359 });
360 }
361 {
362 let Some((addr, port)) = self.cfg.https_sni_addr_template.split_once(':') else {
363 panic!("expected port in https_addr_template");
364 };
365 let port: u16 = port.parse().expect("unexpected port");
366
367 let https = HttpsBalancer {
368 resolver: shared_dns,
369 tls: https_tls,
370 resolve_template: Arc::from(addr),
371 port,
372 metrics: Arc::from(ServerMetrics::new(metrics, "https")),
373 configs: self.configs.clone(),
374 internal_tls: self.cfg.internal_tls,
375 };
376 let (handle, stream) = self.https;
377 server_handles.push(handle);
378 set.spawn_named(|| "https_stream", {
379 let config_set = self.configs.clone();
380 async move {
381 mz_server_core::serve(ServeConfig {
382 server: https,
383 conns: stream,
384 dyncfg: Some(ServeDyncfg {
385 config_set,
386 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
387 }),
388 })
389 .await;
390 warn!("https server exited");
391 }
392 });
393 }
394 {
395 let router = Router::new()
396 .route(
397 "/metrics",
398 routing::get(move |headers: axum::http::HeaderMap| async move {
399 mz_http_util::handle_prometheus(&self.cfg.metrics_registry, headers).await
400 }),
401 )
402 .route(
403 "/api/livez",
404 routing::get(mz_http_util::handle_liveness_check),
405 )
406 .route("/api/readyz", routing::get(handle_readiness_check));
407 let internal_http = InternalHttpServer { router };
408 let (handle, stream) = self.internal_http;
409 server_handles.push(handle);
410 set.spawn_named(|| "internal_http_stream", async move {
411 mz_server_core::serve(ServeConfig {
412 server: internal_http,
413 conns: stream,
414 dyncfg: None,
417 })
418 .await;
419 warn!("internal_http server exited");
420 });
421 }
422 #[cfg(unix)]
423 {
424 let mut sigterm =
425 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
426 set.spawn_named(|| "sigterm_handler", async move {
427 sigterm.recv().await;
428 let wait = SIGTERM_LISTEN_WAIT.get(&self.configs);
429 warn!("received signal TERM - delaying for {:?}!", wait);
430 tokio::time::sleep(wait).await;
431 warn!("sigterm delay complete, dropping server handles");
432 drop(server_handles);
433 });
434 }
435
436 println!("balancerd {} listening...", BUILD_INFO.human_version(None));
437 println!(" TLS enabled: {}", self.cfg.tls.is_some());
438 println!(" pgwire address: {}", pgwire_addr);
439 println!(" HTTPS address: {}", https_addr);
440 println!(" internal HTTP address: {}", internal_http_addr);
441
442 while let Some(res) = set.join_next().await {
444 if let Err(err) = res {
445 error!("serving task failed: {err}")
446 }
447 }
448 Ok(())
449 }
450}
451
452#[allow(clippy::unused_async)]
453async fn handle_readiness_check() -> impl IntoResponse {
454 (StatusCode::OK, "ready")
455}
456
457struct InternalHttpServer {
458 router: Router,
459}
460
461impl mz_server_core::Server for InternalHttpServer {
462 const NAME: &'static str = "internal_http";
463
464 fn handle_connection(
466 &self,
467 conn: Connection,
468 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
469 ) -> mz_server_core::ConnectionHandler {
470 let router = self.router.clone();
471 let service = hyper::service::service_fn(move |req| router.clone().call(req));
472 let conn = TokioIo::new(conn);
473
474 Box::pin(async {
475 let http = hyper::server::conn::http1::Builder::new();
476 http.serve_connection(conn, service).err_into().await
477 })
478 }
479}
480
481struct GaugeGuard {
484 gauge: IntGauge,
485}
486
487impl From<IntGauge> for GaugeGuard {
488 fn from(gauge: IntGauge) -> Self {
489 let _self = Self { gauge };
490 _self.gauge.inc();
491 _self
492 }
493}
494
495impl Drop for GaugeGuard {
496 fn drop(&mut self) {
497 self.gauge.dec();
498 }
499}
500
501#[derive(Clone, Debug)]
502struct ServerMetricsConfig {
503 connection_status: IntCounterVec,
504 active_connections: IntGaugeVec,
505 tenant_connections: IntGaugeVec,
506 tenant_connection_rx: IntCounterVec,
507 tenant_connection_tx: IntCounterVec,
508 tenant_pgwire_sni_count: IntCounterVec,
509}
510
511impl ServerMetricsConfig {
512 fn register_into(registry: &MetricsRegistry) -> Self {
513 let connection_status = registry.register(metric!(
514 name: "mz_balancer_connection_status",
515 help: "Count of completed network connections, by status",
516 var_labels: ["source", "status"],
517 ));
518 let active_connections = registry.register(metric!(
519 name: "mz_balancer_connection_active",
520 help: "Count of currently open network connections.",
521 var_labels: ["source"],
522 ));
523 let tenant_connections = registry.register(metric!(
524 name: "mz_balancer_tenant_connection_active",
525 help: "Count of opened network connections by tenant.",
526 var_labels: ["source", "tenant"]
527 ));
528 let tenant_connection_rx = registry.register(metric!(
529 name: "mz_balancer_tenant_connection_rx",
530 help: "Number of bytes received from a client for a tenant.",
531 var_labels: ["source", "tenant"],
532 ));
533 let tenant_connection_tx = registry.register(metric!(
534 name: "mz_balancer_tenant_connection_tx",
535 help: "Number of bytes sent to a client for a tenant.",
536 var_labels: ["source", "tenant"],
537 ));
538 let tenant_pgwire_sni_count = registry.register(metric!(
539 name: "mz_balancer_tenant_pgwire_sni_count",
540 help: "Count of pgwire connections that have and do not have SNI available per tenant.",
541 var_labels: ["tenant", "has_sni"],
542 ));
543 Self {
544 connection_status,
545 active_connections,
546 tenant_connections,
547 tenant_connection_rx,
548 tenant_connection_tx,
549 tenant_pgwire_sni_count,
550 }
551 }
552}
553
554#[derive(Clone, Debug)]
555struct ServerMetrics {
556 inner: ServerMetricsConfig,
557 source: &'static str,
558}
559
560impl ServerMetrics {
561 fn new(inner: ServerMetricsConfig, source: &'static str) -> Self {
562 let self_ = Self { inner, source };
563
564 self_.connection_status(false);
567 self_.connection_status(true);
568 drop(self_.active_connections());
569
570 self_
571 }
572
573 fn connection_status(&self, is_ok: bool) -> IntCounter {
574 self.inner
575 .connection_status
576 .with_label_values(&[self.source, Self::status_label(is_ok)])
577 }
578
579 fn active_connections(&self) -> GaugeGuard {
580 self.inner
581 .active_connections
582 .with_label_values(&[self.source])
583 .into()
584 }
585
586 fn tenant_connections(&self, tenant: &str) -> GaugeGuard {
587 self.inner
588 .tenant_connections
589 .with_label_values(&[self.source, tenant])
590 .into()
591 }
592
593 fn tenant_connections_rx(&self, tenant: &str) -> IntCounter {
594 self.inner
595 .tenant_connection_rx
596 .with_label_values(&[self.source, tenant])
597 }
598
599 fn tenant_connections_tx(&self, tenant: &str) -> IntCounter {
600 self.inner
601 .tenant_connection_tx
602 .with_label_values(&[self.source, tenant])
603 }
604
605 fn tenant_pgwire_sni_count(&self, tenant: &str, has_sni: bool) -> IntCounter {
606 self.inner
607 .tenant_pgwire_sni_count
608 .with_label_values(&[tenant, &has_sni.to_string()])
609 }
610
611 fn status_label(is_ok: bool) -> &'static str {
612 if is_ok { "success" } else { "error" }
613 }
614}
615
616pub enum CancellationResolver {
617 Directory(PathBuf),
618 Static(String),
619}
620
621struct PgwireBalancer {
622 tls: Option<ReloadingTlsConfig>,
623 internal_tls: bool,
624 cancellation_resolver: Arc<CancellationResolver>,
625 resolver: Arc<BalancerResolver>,
626 metrics: ServerMetrics,
627 now: NowFn,
628}
629
630impl PgwireBalancer {
631 #[mz_ore::instrument(level = "debug")]
632 async fn run<'a, A>(
633 conn: &'a mut FramedConn<A>,
634 version: i32,
635 params: BTreeMap<String, String>,
636 resolver: &BalancerResolver,
637 tls_mode: Option<TlsMode>,
638 internal_tls: bool,
639 metrics: &ServerMetrics,
640 ) -> Result<(), io::Error>
641 where
642 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
643 {
644 if version != VERSION_3 {
645 return conn
646 .send(ErrorResponse::fatal(
647 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
648 "server does not support the client's requested protocol version",
649 ))
650 .await;
651 }
652
653 let Some(user) = params.get("user") else {
654 return conn
655 .send(ErrorResponse::fatal(
656 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
657 "user parameter required",
658 ))
659 .await;
660 };
661
662 if let Err(err) = conn.inner().ensure_tls_compatibility(&tls_mode) {
663 return conn.send(err).await;
664 }
665
666 let resolved = match resolver.resolve(conn, user, metrics).await {
667 Ok(v) => v,
668 Err(err) => {
669 let sql_state = match &err {
670 ResolveError::InvalidPassword => SqlState::INVALID_PASSWORD,
671 ResolveError::Client(details) => {
672 warn!("client-caused connection failure: {details:#}");
673 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
674 }
675 ResolveError::Upstream(details) => {
676 warn!("upstream not available: {details:#}");
677 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
678 }
679 ResolveError::Internal(details) => {
680 error!("resolving connection destination: {details:#}");
681 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
682 }
683 };
684 return conn
685 .send(ErrorResponse::fatal(sql_state, err.to_string()))
686 .await;
687 }
688 };
689
690 let _active_guard = resolved
691 .tenant
692 .as_ref()
693 .map(|tenant| metrics.tenant_connections(tenant));
694 let mut mz_stream =
695 match Self::init_stream(conn, resolved.addr, resolved.password, params, internal_tls)
696 .await
697 {
698 Ok(stream) => stream,
699 Err(e) => {
700 error!("failed to connect to upstream server: {e}");
701 return conn
702 .send(ErrorResponse::fatal(
703 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
704 "upstream server not available",
705 ))
706 .await;
707 }
708 };
709
710 let mut client_counter = CountingConn::new(conn.inner_mut());
711
712 let res = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
715 if let Some(tenant) = &resolved.tenant {
716 metrics
717 .tenant_connections_tx(tenant)
718 .inc_by(u64::cast_from(client_counter.written));
719 metrics
720 .tenant_connections_rx(tenant)
721 .inc_by(u64::cast_from(client_counter.read));
722 }
723 res?;
724
725 Ok(())
726 }
727
728 #[mz_ore::instrument(level = "debug")]
729 async fn init_stream<'a, A>(
730 conn: &'a mut FramedConn<A>,
731 envd_addr: SocketAddr,
732 password: Option<String>,
733 params: BTreeMap<String, String>,
734 internal_tls: bool,
735 ) -> Result<Conn<TcpStream>, anyhow::Error>
736 where
737 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
738 {
739 let mut mz_stream = TcpStream::connect(envd_addr).await?;
740 let mut buf = BytesMut::new();
741
742 let mut mz_stream = if internal_tls {
743 FrontendStartupMessage::SslRequest.encode(&mut buf)?;
744 mz_stream.write_all(&buf).await?;
745 buf.clear();
746 let mut maybe_ssl_request_response = [0u8; 1];
747 let nread =
748 netio::read_exact_or_eof(&mut mz_stream, &mut maybe_ssl_request_response).await?;
749 if nread == 1 && maybe_ssl_request_response == [ACCEPT_SSL_ENCRYPTION] {
750 let mut builder =
752 SslConnector::builder(SslMethod::tls()).expect("Error creating builder.");
753 builder.set_verify(SslVerifyMode::NONE);
755 let mut ssl = builder
756 .build()
757 .configure()?
758 .into_ssl(&envd_addr.to_string())?;
759 ssl.set_connect_state();
760 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
761 } else {
762 Conn::Unencrypted(mz_stream)
763 }
764 } else {
765 Conn::Unencrypted(mz_stream)
766 };
767
768 let startup = FrontendStartupMessage::Startup {
770 version: VERSION_3,
771 params,
772 };
773 startup.encode(&mut buf)?;
774 mz_stream.write_all(&buf).await?;
775 let client_stream = conn.inner_mut();
776
777 if password.is_none() {
793 return Ok(mz_stream);
794 }
795
796 let mut maybe_auth_frame = [0; 1 + 4 + 4];
800 let nread = netio::read_exact_or_eof(&mut mz_stream, &mut maybe_auth_frame).await?;
801 const AUTH_PASSWORD_CLEARTEXT: [u8; 9] = [b'R', 0, 0, 0, 8, 0, 0, 0, 3];
804 if nread == AUTH_PASSWORD_CLEARTEXT.len()
805 && maybe_auth_frame == AUTH_PASSWORD_CLEARTEXT
806 && password.is_some()
807 {
808 let Some(password) = password else {
810 unreachable!("verified some above");
811 };
812 let password = FrontendMessage::Password { password };
813 buf.clear();
814 password.encode(&mut buf)?;
815 mz_stream.write_all(&buf).await?;
816 mz_stream.flush().await?;
817 } else {
818 client_stream.write_all(&maybe_auth_frame[0..nread]).await?;
821 }
822
823 Ok(mz_stream)
824 }
825}
826
827impl mz_server_core::Server for PgwireBalancer {
828 const NAME: &'static str = "pgwire_balancer";
829
830 fn handle_connection(
831 &self,
832 conn: Connection,
833 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
834 ) -> mz_server_core::ConnectionHandler {
835 let tls = self.tls.clone();
836 let internal_tls = self.internal_tls;
837 let resolver = Arc::clone(&self.resolver);
838 let inner_metrics = self.metrics.clone();
839 let outer_metrics = self.metrics.clone();
840 let cancellation_resolver = Arc::clone(&self.cancellation_resolver);
841 let conn_uuid = epoch_to_uuid_v7(&(self.now)());
842 let peer_addr = conn.peer_addr();
843 conn.uuid_handle().set(conn_uuid);
844 Box::pin(async move {
845 let active_guard = outer_metrics.active_connections();
848 let result: Result<(), anyhow::Error> = async move {
849 let mut conn = Conn::Unencrypted(conn);
850 loop {
851 let message = decode_startup(&mut conn).await?;
852 conn = match message {
853 None => return Ok(()),
857
858 Some(FrontendStartupMessage::Startup {
859 version,
860 mut params,
861 }) => {
862 let mut conn = FramedConn::new(conn);
863 let rejected =
864 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION;
865 let peer_addr = match peer_addr {
866 Ok(addr) => addr.ip(),
867 Err(e) => {
868 error!("Invalid peer_addr {:?}", e);
869 return Ok(conn
870 .send(ErrorResponse::fatal(
871 rejected,
872 "invalid peer address",
873 ))
874 .await?);
875 }
876 };
877 debug!(
878 %conn_uuid, %peer_addr,
879 "starting new pgwire connection in balancer",
880 );
881 let prev =
882 params.insert(CONN_UUID_KEY.to_string(), conn_uuid.to_string());
883 if prev.is_some() {
884 return Ok(conn
885 .send(ErrorResponse::fatal(
886 rejected,
887 format!("invalid parameter '{CONN_UUID_KEY}'"),
888 ))
889 .await?);
890 }
891
892 let forwarded_for = params.insert(
893 MZ_FORWARDED_FOR_KEY.to_string(),
894 peer_addr.to_string().clone(),
895 );
896 if let Some(_) = forwarded_for {
897 return Ok(conn
898 .send(ErrorResponse::fatal(
899 rejected,
900 format!("invalid parameter '{MZ_FORWARDED_FOR_KEY}'"),
901 ))
902 .await?);
903 };
904
905 Self::run(
906 &mut conn,
907 version,
908 params,
909 &resolver,
910 tls.map(|tls| tls.mode),
911 internal_tls,
912 &inner_metrics,
913 )
914 .await?;
915 conn.flush().await?;
916 return Ok(());
917 }
918
919 Some(FrontendStartupMessage::CancelRequest {
920 conn_id,
921 secret_key,
922 }) => {
923 spawn(|| "cancel request", async move {
924 cancel_request(conn_id, secret_key, &cancellation_resolver).await;
925 });
926 return Ok(());
929 }
930
931 Some(FrontendStartupMessage::SslRequest) => match (conn, &tls) {
932 (Conn::Unencrypted(mut conn), Some(tls)) => {
933 conn.write_all(&[ACCEPT_SSL_ENCRYPTION]).await?;
934 let mut ssl_stream =
935 SslStream::new(Ssl::new(&tls.context.get())?, conn)?;
936 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
937 let _ = ssl_stream.get_mut().shutdown().await;
938 return Err(e.into());
939 }
940 Conn::Ssl(ssl_stream)
941 }
942 (mut conn, _) => {
943 conn.write_all(&[REJECT_ENCRYPTION]).await?;
944 conn
945 }
946 },
947
948 Some(FrontendStartupMessage::GssEncRequest) => {
949 conn.write_all(&[REJECT_ENCRYPTION]).await?;
950 conn
951 }
952 }
953 }
954 }
955 .await;
956 drop(active_guard);
957 outer_metrics.connection_status(result.is_ok()).inc();
958 Ok(())
959 })
960 }
961}
962
963struct CountingConn<C> {
965 inner: C,
966 read: usize,
967 written: usize,
968}
969
970impl<C> CountingConn<C> {
971 fn new(inner: C) -> Self {
972 CountingConn {
973 inner,
974 read: 0,
975 written: 0,
976 }
977 }
978}
979
980impl<C> AsyncRead for CountingConn<C>
981where
982 C: AsyncRead + Unpin,
983{
984 fn poll_read(
985 self: Pin<&mut Self>,
986 cx: &mut std::task::Context<'_>,
987 buf: &mut io::ReadBuf<'_>,
988 ) -> std::task::Poll<std::io::Result<()>> {
989 let counter = self.get_mut();
990 let pin = Pin::new(&mut counter.inner);
991 let bytes = buf.filled().len();
992 let poll = pin.poll_read(cx, buf);
993 let bytes = buf.filled().len() - bytes;
994 if let std::task::Poll::Ready(Ok(())) = poll {
995 counter.read += bytes
996 }
997 poll
998 }
999}
1000
1001impl<C> AsyncWrite for CountingConn<C>
1002where
1003 C: AsyncWrite + Unpin,
1004{
1005 fn poll_write(
1006 self: Pin<&mut Self>,
1007 cx: &mut std::task::Context<'_>,
1008 buf: &[u8],
1009 ) -> std::task::Poll<Result<usize, std::io::Error>> {
1010 let counter = self.get_mut();
1011 let pin = Pin::new(&mut counter.inner);
1012 let poll = pin.poll_write(cx, buf);
1013 if let std::task::Poll::Ready(Ok(bytes)) = poll {
1014 counter.written += bytes
1015 }
1016 poll
1017 }
1018
1019 fn poll_flush(
1020 self: Pin<&mut Self>,
1021 cx: &mut std::task::Context<'_>,
1022 ) -> std::task::Poll<Result<(), std::io::Error>> {
1023 let counter = self.get_mut();
1024 let pin = Pin::new(&mut counter.inner);
1025 pin.poll_flush(cx)
1026 }
1027
1028 fn poll_shutdown(
1029 self: Pin<&mut Self>,
1030 cx: &mut std::task::Context<'_>,
1031 ) -> std::task::Poll<Result<(), std::io::Error>> {
1032 let counter = self.get_mut();
1033 let pin = Pin::new(&mut counter.inner);
1034 pin.poll_shutdown(cx)
1035 }
1036}
1037
1038async fn cancel_request(
1055 conn_id: u32,
1056 secret_key: u32,
1057 cancellation_resolver: &CancellationResolver,
1058) {
1059 let suffix = conn_id_org_uuid(conn_id);
1060 let contents = match cancellation_resolver {
1061 CancellationResolver::Directory(dir) => {
1062 let path = dir.join(&suffix);
1063 match std::fs::read_to_string(&path) {
1064 Ok(contents) => contents,
1065 Err(err) => {
1066 error!("could not read cancel file {path:?}: {err}");
1067 return;
1068 }
1069 }
1070 }
1071 CancellationResolver::Static(addr) => addr.to_owned(),
1072 };
1073 let mut all_ips = Vec::new();
1074 for addr in contents.lines() {
1075 let addr = addr.trim();
1076 if addr.is_empty() {
1077 continue;
1078 }
1079 match tokio::net::lookup_host(addr).await {
1080 Ok(ips) => all_ips.extend(ips),
1081 Err(err) => {
1082 error!("{addr} failed resolution: {err}");
1083 }
1084 }
1085 }
1086 let mut buf = BytesMut::with_capacity(16);
1087 let msg = FrontendStartupMessage::CancelRequest {
1088 conn_id,
1089 secret_key,
1090 };
1091 msg.encode(&mut buf).expect("must encode");
1092 let buf = buf.freeze();
1093 for ip in all_ips {
1094 debug!("cancelling {suffix} to {ip}");
1095 let buf = buf.clone();
1096 spawn(|| "cancel request for ip", async move {
1097 let send = async {
1098 let mut stream = TcpStream::connect(&ip).await?;
1099 stream.write_all(&buf).await?;
1100 stream.shutdown().await?;
1101 Ok::<_, io::Error>(())
1102 };
1103 if let Err(err) = send.await {
1104 error!("error mirroring cancel to {ip}: {err}");
1105 }
1106 });
1107 }
1108}
1109
1110struct HttpsBalancer {
1111 resolver: Arc<TenantDnsResolver>,
1112 tls: Option<ReloadingSslContext>,
1113 resolve_template: Arc<str>,
1114 port: u16,
1115 metrics: Arc<ServerMetrics>,
1116 configs: ConfigSet,
1117 internal_tls: bool,
1118}
1119
1120impl HttpsBalancer {
1121 async fn resolve(
1122 resolver: &TenantDnsResolver,
1123 resolve_template: &str,
1124 port: u16,
1125 servername: Option<&str>,
1126 ) -> Result<ResolvedAddr, anyhow::Error> {
1127 let (addr, tenant) = match servername {
1128 Some(sni) => resolver.resolve_sni(resolve_template, port, sni).await?,
1129 None => {
1130 debug!("https hostname (no SNI): {}:{}", resolve_template, port);
1133 resolver.resolve(resolve_template, port).await?
1134 }
1135 };
1136
1137 Ok(ResolvedAddr {
1138 addr,
1139 password: None,
1140 tenant,
1141 })
1142 }
1143}
1144
1145fn extract_tenant_from_cname(cname: &str) -> Option<String> {
1158 let mut parts = cname.split('.');
1159 let _service = parts.next();
1160 let Some(namespace) = parts.next() else {
1161 return None;
1162 };
1163 let Some((_, namespace)) = namespace.split_once('-') else {
1165 return None;
1166 };
1167 let Some((tenant, _)) = namespace.rsplit_once('-') else {
1169 return None;
1170 };
1171 let Ok(tenant) = Uuid::parse_str(tenant) else {
1174 error!("cname tenant not a uuid: {tenant}");
1175 return None;
1176 };
1177 Some(tenant.to_string())
1178}
1179
1180fn strip_ipv6_brackets(host: &str) -> &str {
1185 host.strip_prefix('[')
1186 .and_then(|h| h.strip_suffix(']'))
1187 .unwrap_or(host)
1188}
1189
1190impl mz_server_core::Server for HttpsBalancer {
1191 const NAME: &'static str = "https_balancer";
1192
1193 fn handle_connection(
1195 &self,
1196 conn: Connection,
1197 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1198 ) -> mz_server_core::ConnectionHandler {
1199 let tls_context = self.tls.clone();
1200 let internal_tls = self.internal_tls.clone();
1201 let resolver = Arc::clone(&self.resolver);
1202 let resolve_template = Arc::clone(&self.resolve_template);
1203 let port = self.port;
1204 let inner_metrics = Arc::clone(&self.metrics);
1205 let outer_metrics = Arc::clone(&self.metrics);
1206 let peer_addr = conn.peer_addr();
1207 let inject_proxy_headers = INJECT_PROXY_PROTOCOL_HEADER_HTTP.get(&self.configs);
1208 Box::pin(async move {
1209 let active_guard = inner_metrics.active_connections();
1210 let result: Result<_, anyhow::Error> = Box::pin(async move {
1211 let peer_addr = peer_addr.context("fetching peer addr")?;
1212 let (mut client_stream, servername): (Box<dyn ClientStream>, Option<String>) =
1213 match tls_context {
1214 Some(tls_context) => {
1215 let mut ssl_stream =
1216 SslStream::new(Ssl::new(&tls_context.get())?, conn)?;
1217 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1218 let _ = ssl_stream.get_mut().shutdown().await;
1219 return Err(e.into());
1220 }
1221 let servername: Option<String> =
1222 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1223 match sn.split_once('.') {
1224 Some((left, _right)) => left,
1225 None => sn,
1226 }
1227 .into()
1228 });
1229 debug!("Found sni servername: {servername:?} (https)");
1230 (Box::new(ssl_stream), servername)
1231 }
1232 _ => (Box::new(conn), None),
1233 };
1234 let resolved =
1235 Self::resolve(&resolver, &resolve_template, port, servername.as_deref())
1236 .await?;
1237 let inner_active_guard = resolved
1238 .tenant
1239 .as_ref()
1240 .map(|tenant| inner_metrics.tenant_connections(tenant));
1241 let mut mz_stream = match TcpStream::connect(resolved.addr).await {
1242 Ok(stream) => stream,
1243 Err(e) => {
1244 error!("failed to connect to upstream server: {e}");
1245 let body = "upstream server not available";
1246 let response = format!(
1252 "HTTP/1.1 502 Bad Gateway\r\n\
1253 Content-Type: text/plain\r\n\
1254 Content-Length: {}\r\n\
1255 Connection: close\r\n\
1256 \r\n\
1257 {}",
1258 body.len(),
1259 body
1260 );
1261 let _ = client_stream.write_all(response.as_bytes()).await;
1262 let _ = client_stream.shutdown().await;
1263 return Ok(());
1264 }
1265 };
1266
1267 if inject_proxy_headers {
1268 let addrs = ProxiedAddress::stream(peer_addr, resolved.addr);
1270 let header = ProxyHeader::with_address(addrs);
1271 let mut buf = [0u8; 1024];
1272 let len = header.encode_to_slice_v2(&mut buf)?;
1273 mz_stream.write_all(&buf[..len]).await?;
1274 }
1275
1276 let mut mz_stream = if internal_tls {
1277 let mut builder =
1279 SslConnector::builder(SslMethod::tls()).expect("Error creating builder.");
1280 builder.set_verify(SslVerifyMode::NONE);
1282 let mut ssl = builder
1283 .build()
1284 .configure()?
1285 .into_ssl(&resolved.addr.to_string())?;
1286 ssl.set_connect_state();
1287 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1288 } else {
1289 Conn::Unencrypted(mz_stream)
1290 };
1291
1292 let mut client_counter = CountingConn::new(client_stream);
1293
1294 let _ = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1298 if let Some(tenant) = &resolved.tenant {
1299 inner_metrics
1300 .tenant_connections_tx(tenant)
1301 .inc_by(u64::cast_from(client_counter.written));
1302 inner_metrics
1303 .tenant_connections_rx(tenant)
1304 .inc_by(u64::cast_from(client_counter.read));
1305 }
1306 drop(inner_active_guard);
1307 Ok(())
1308 })
1309 .await;
1310 drop(active_guard);
1311 outer_metrics.connection_status(result.is_ok()).inc();
1312 if let Err(e) = result {
1313 debug!("connection error: {e}");
1314 }
1315 Ok(())
1316 })
1317 }
1318}
1319
1320#[derive(Debug)]
1323pub struct SniTemplate {
1324 pub template: String,
1325 pub port: u16,
1326}
1327
1328trait ClientStream: AsyncRead + AsyncWrite + Unpin + Send {}
1329impl<T: AsyncRead + AsyncWrite + Unpin + Send> ClientStream for T {}
1330
1331#[derive(Debug)]
1332pub enum BalancerResolver {
1333 Static(String),
1334 MultiTenant {
1335 dns: Arc<TenantDnsResolver>,
1336 frontegg: FronteggResolver,
1337 sni: Option<SniTemplate>,
1338 },
1339}
1340
1341#[derive(Debug, thiserror::Error)]
1347enum ResolveError {
1348 #[error("invalid password")]
1349 InvalidPassword,
1350 #[error("internal error")]
1355 Client(#[source] anyhow::Error),
1356 #[error("upstream server not available")]
1363 Upstream(#[source] anyhow::Error),
1364 #[error("internal error")]
1366 Internal(#[from] anyhow::Error),
1367}
1368
1369impl From<io::Error> for ResolveError {
1370 fn from(e: io::Error) -> Self {
1371 ResolveError::Internal(e.into())
1372 }
1373}
1374
1375impl BalancerResolver {
1376 pub fn shared_dns(&self) -> Option<Arc<TenantDnsResolver>> {
1379 match self {
1380 BalancerResolver::Static(_) => None,
1381 BalancerResolver::MultiTenant { dns, .. } => Some(Arc::clone(dns)),
1382 }
1383 }
1384
1385 async fn resolve<A>(
1386 &self,
1387 conn: &mut FramedConn<A>,
1388 user: &str,
1389 metrics: &ServerMetrics,
1390 ) -> Result<ResolvedAddr, ResolveError>
1391 where
1392 A: AsyncRead + AsyncWrite + Unpin,
1393 {
1394 match self {
1395 BalancerResolver::MultiTenant {
1396 dns: dns_resolver,
1397 frontegg:
1398 FronteggResolver {
1399 auth,
1400 addr_template,
1401 },
1402 sni: sni_resolver,
1403 } => {
1404 let servername = match conn.inner() {
1405 Conn::Ssl(ssl_stream) => {
1406 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1407 match sn.split_once('.') {
1408 Some((left, _right)) => left,
1409 None => sn,
1410 }
1411 })
1412 }
1413 Conn::Unencrypted(_) => None,
1414 };
1415 let has_sni = servername.is_some();
1416 let resolved_addr = match (servername, sni_resolver.as_ref()) {
1417 (Some(servername), Some(SniTemplate { template, port })) => {
1418 let (addr, tenant) = dns_resolver
1422 .resolve_sni(template, *port, servername)
1423 .await
1424 .map_err(ResolveError::Upstream)?;
1425 debug!("pgwire SNI resolved tenant: {:?}", tenant);
1426 ResolvedAddr {
1427 addr,
1428 password: None,
1429 tenant,
1430 }
1431 }
1432 _ => {
1433 conn.send(BackendMessage::AuthenticationCleartextPassword)
1434 .await?;
1435 conn.flush().await?;
1436 let password = match conn.recv().await? {
1437 Some(FrontendMessage::Password { password }) => password,
1438 _ => {
1439 return Err(ResolveError::Client(anyhow::anyhow!(
1440 "expected Password message"
1441 )));
1442 }
1443 };
1444
1445 let auth_response = auth.authenticate(user, &password, None).await;
1449 let auth_session = match auth_response {
1450 Ok((auth_session, _)) => auth_session,
1451 Err(e) => {
1452 warn!("pgwire connection failed authentication: {}", e);
1453 return Err(ResolveError::InvalidPassword);
1454 }
1455 };
1456
1457 let hostname_with_port =
1458 addr_template.replace("{}", &auth_session.tenant_id().to_string());
1459 let (hostname, port_str) = hostname_with_port
1460 .rsplit_once(':')
1461 .ok_or_else(|| anyhow::anyhow!("port required in addr_template"))?;
1462 let port: u16 = port_str.parse().with_context(|| {
1463 format!("invalid port in addr_template: {}", port_str)
1464 })?;
1465 let addr = dns_resolver
1469 .resolve_addr(hostname, port)
1470 .await
1471 .map_err(ResolveError::Upstream)?;
1472 let tenant = auth_session.tenant_id().to_string();
1473 debug!("Frontegg resolved tenant: {}", tenant);
1474 ResolvedAddr {
1475 addr,
1476 password: Some(password),
1477 tenant: Some(tenant),
1478 }
1479 }
1480 };
1481 metrics
1482 .tenant_pgwire_sni_count(
1483 resolved_addr.tenant.as_deref().unwrap_or("unknown"),
1484 has_sni,
1485 )
1486 .inc();
1487
1488 Ok(resolved_addr)
1489 }
1490 BalancerResolver::Static(addr) => {
1491 let Some(addr) = tokio::net::lookup_host(addr).await?.next() else {
1494 return Err(anyhow::anyhow!("{addr} did not resolve to any addresses").into());
1495 };
1496
1497 Ok(ResolvedAddr {
1498 addr,
1499 password: None,
1500 tenant: None,
1501 })
1502 }
1503 }
1504 }
1505}
1506
1507fn create_resolver() -> Result<TokioResolver, anyhow::Error> {
1515 let (config, mut opts) = read_system_conf().context("reading system DNS configuration")?;
1516 opts.cache_size = 0;
1517 opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
1519
1520 Ok(
1521 Resolver::builder_with_config(config, TokioRuntimeProvider::default())
1522 .with_options(opts)
1523 .build()?,
1524 )
1525}
1526
1527#[derive(Debug)]
1533pub struct TenantDnsResolver {
1534 resolver: TokioResolver,
1535}
1536
1537impl TenantDnsResolver {
1538 pub fn new() -> Result<Self, anyhow::Error> {
1541 Ok(Self {
1542 resolver: create_resolver()?,
1543 })
1544 }
1545
1546 async fn resolve_cname(&self, hostname: &str) -> Option<String> {
1548 match self.resolver.lookup(hostname, RecordType::CNAME).await {
1549 Ok(cname_response) => {
1550 if let Some(cname_record) = cname_response.answers().first() {
1551 if let RData::CNAME(cname_data) = &cname_record.data {
1552 let cname = cname_data.to_string();
1553 debug!("CNAME for {}: {}", hostname, cname);
1554 return Some(cname);
1555 }
1556 }
1557 None
1558 }
1559 Err(e) => {
1560 debug!("CNAME lookup failed for {}: {}", hostname, e);
1561 None
1562 }
1563 }
1564 }
1565
1566 async fn resolve_a(&self, hostname: &str) -> Result<LookupIp, anyhow::Error> {
1568 self.resolver
1569 .lookup_ip(hostname)
1570 .await
1571 .with_context(|| format!("resolving A records for {}", hostname))
1572 }
1573
1574 pub async fn resolve_sni(
1585 &self,
1586 template: &str,
1587 port: u16,
1588 servername: &str,
1589 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
1590 let hostname = template.replace("{}", servername);
1591 debug!("SNI hostname: {}", hostname);
1592 self.resolve(&hostname, port).await
1593 }
1594
1595 async fn resolve_addr(&self, host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
1598 let host = strip_ipv6_brackets(host);
1599 if let Ok(ip) = host.parse::<IpAddr>() {
1603 return Ok(SocketAddr::new(ip, port));
1604 }
1605 Self::first_addr(self.resolve_a(host).await?, port)
1606 }
1607
1608 async fn resolve(
1612 &self,
1613 host: &str,
1614 port: u16,
1615 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
1616 let host = strip_ipv6_brackets(host);
1617 if let Ok(ip) = host.parse::<IpAddr>() {
1619 return Ok((SocketAddr::new(ip, port), None));
1620 }
1621
1622 let (ips, tenant) = if let Some(cname) = self.resolve_cname(host).await {
1624 let tenant = extract_tenant_from_cname(&cname);
1625 (self.resolve_a(&cname).await?, tenant)
1626 } else {
1627 (self.resolve_a(host).await?, None)
1628 };
1629
1630 Ok((Self::first_addr(ips, port)?, tenant))
1631 }
1632
1633 fn first_addr(ips: LookupIp, port: u16) -> Result<SocketAddr, anyhow::Error> {
1635 ips.iter()
1636 .next()
1637 .map(|ip| SocketAddr::new(ip, port))
1638 .ok_or_else(|| anyhow::anyhow!("no A records found in DNS response"))
1639 }
1640}
1641
1642#[derive(Debug)]
1643pub struct FronteggResolver {
1644 pub auth: FronteggAuthentication,
1645 pub addr_template: String,
1646}
1647
1648#[derive(Debug)]
1649struct ResolvedAddr {
1650 addr: SocketAddr,
1651 password: Option<String>,
1652 tenant: Option<String>,
1653}
1654
1655#[cfg(test)]
1656mod tests {
1657 use super::*;
1658
1659 #[mz_ore::test]
1660 fn test_tenant() {
1661 let tests = vec![
1662 ("", None),
1663 (
1664 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
1665 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1666 ),
1667 (
1668 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local.",
1671 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1672 ),
1673 (
1674 "service.something-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.ssvvcc.cloister.faraway",
1676 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1677 ),
1678 (
1679 "environmentd.environment-58cd23ffa4d74bd0ad85a6ff29cc86c3-0.svc.cluster.local",
1681 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1682 ),
1683 (
1684 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-1234.svc.cluster.local",
1686 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1687 ),
1688 (
1689 "environmentd.environment-58CD23FF-A4D7-4BD0-AD85-A6FF29CC86C3-0.svc.cluster.local",
1691 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1692 ),
1693 (
1694 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3.svc.cluster.local",
1696 None,
1697 ),
1698 (
1699 "environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
1701 None,
1702 ),
1703 (
1704 "environmentd.environment-8cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
1706 None,
1707 ),
1708 ];
1709 for (name, expect) in tests {
1710 let cname = extract_tenant_from_cname(name);
1711 assert_eq!(
1712 cname.as_deref(),
1713 expect,
1714 "{name} got {cname:?} expected {expect:?}"
1715 );
1716 }
1717 }
1718
1719 #[mz_ore::test]
1720 fn test_strip_ipv6_brackets() {
1721 assert_eq!(strip_ipv6_brackets("[::1]"), "::1");
1722 assert_eq!(strip_ipv6_brackets("[2001:db8::1]"), "2001:db8::1");
1723 assert_eq!(strip_ipv6_brackets("127.0.0.1"), "127.0.0.1");
1725 assert_eq!(strip_ipv6_brackets("host.example.com"), "host.example.com");
1726 assert_eq!(strip_ipv6_brackets("[unclosed"), "[unclosed");
1727 assert_eq!(strip_ipv6_brackets("unopened]"), "unopened]");
1728 }
1729}