Skip to main content

mz_balancerd/
lib.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! The balancerd service is a horizontally scalable, stateless, multi-tenant ingress router for
11//! pgwire and HTTPS connections.
12//!
13//! It listens on pgwire and HTTPS ports. When a new pgwire connection starts, the requested user is
14//! authenticated with frontegg from which a tenant id is returned. From that a target internal
15//! hostname is resolved to an IP address, and the connection is proxied to that address which has a
16//! running environmentd's pgwire port. When a new HTTPS connection starts, its SNI hostname is used
17//! to generate an internal hostname that is resolved to an IP address, which is similarly proxied.
18
19mod 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
84/// Balancer build information.
85pub const BUILD_INFO: BuildInfo = build_info!();
86
87pub struct BalancerConfig {
88    /// Info about which version of the code is running.
89    build_version: Version,
90    /// Listen address for internal HTTP health and metrics server.
91    internal_http_listen_addr: SocketAddr,
92    /// Listen address for pgwire connections.
93    pgwire_listen_addr: SocketAddr,
94    /// Listen address for HTTPS connections.
95    https_listen_addr: SocketAddr,
96    /// DNS resolver for pgwire cancellation requests
97    cancellation_resolver: CancellationResolver,
98    /// DNS resolver.
99    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/// Prometheus monitoring metrics.
162#[derive(Debug)]
163pub struct BalancerMetrics {
164    _uptime: ComputedGauge,
165}
166
167impl BalancerMetrics {
168    /// Returns a new [BalancerMetrics] instance connected to the registry in cfg.
169    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        // Configure dyncfg sync
206        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) // exclude this user from the dashboard
239                                .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                // If there's an Error, log but continue anyway. If LD is down
281                // we have no way of fetching the previous value of the flag
282                // (unlike the adapter, but it has a durable catalog). The
283                // ConfigSet defaults have been chosen to be good enough if this
284                // is the case.
285                .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        // The HTTPS balancer always resolves through a TenantDnsResolver. In
326        // multi-tenant mode it shares the pgwire resolver so both listeners use
327        // one resolver. In static mode pgwire does not resolve through it, so
328        // HTTPS gets its own.
329        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                    // Disable graceful termination because our internal
415                    // monitoring keeps persistent HTTP connections open.
416                    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        // Wait for all tasks to exit, which can happen on SIGTERM.
443        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    // TODO(jkosh44) consider forwarding the connection UUID to the adapter.
465    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
481/// Wraps an IntGauge and automatically `inc`s on init and `drop`s on drop. Callers should not call
482/// `inc().`. Useful for handling multiple task exit points, for example in the case of a panic.
483struct 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        // Pre-initialize labels we are planning to use to ensure they are all always emitted as
565        // time series.
566        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        // Now blindly shuffle bytes back and forth until closed.
713        // TODO: Limit total memory use.
714        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                // do a TLS handshake
751                let mut builder =
752                    SslConnector::builder(SslMethod::tls()).expect("Error creating builder.");
753                // environmentd doesn't yet have a cert we trust, so for now disable verification.
754                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        // Send initial startup and password messages.
769        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        // This early return is important in self managed with SASL mode.
778        // The below code specifically looks for cleartext password requests, but in SASL mode
779        // the server will send a different message type (SASLInitialResponse) that we should
780        // not try to interpret or respond to.
781        // "Why not? That code looks like it should fall back fine?" You may ask.
782        // The below block unconditionally reads 9 bytes from the server. If we don't have
783        // a password or the message isn't a cleartext password request, we forward those 9 bytes
784        // to the client. Then we return the stream to the caller, who will continue shuffling bytes.
785        // The problem is that with TLS enabled between balancerd <-> client, flushing the first 9 bytes
786        // before copying bidirectionally will have the side effect of splitting the auth handshake into
787        // two SSL records. Pgbouncer misbehaves in this scenario, and fails the connection.
788        // PGbouncer shouldn't do this! It's a common footgun of protocols over TLS.
789        // So common in fact that PGbouncer already hit and fixed this issue on the bouncer <-> client side:
790        // once before: https://github.com/pgbouncer/pgbouncer/pull/1058.
791        // We will work to upstream a fix, but in the meantime, this early return avoids the issue entirely.
792        if password.is_none() {
793            return Ok(mz_stream);
794        }
795
796        // Read a single backend message, which may be a password request. Send ours if so.
797        // Otherwise start shuffling bytes. message type (len 1, 'R') + message len (len 4, 8_i32) +
798        // auth type (len 4, 3_i32).
799        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        // 'R' for auth message, 0008 for message length, 0003 for password cleartext variant.
802        // See: https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-AUTHENTICATIONCLEARTEXTPASSWORD
803        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            // If we got exactly a cleartext password request and have one, send it.
809            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            // Otherwise pass on the bytes we just got. This *might* even be a password request, but
819            // we don't have a password. In which case it can be forwarded up to the client.
820            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            // TODO: Try to merge this with pgwire/server.rs to avoid the duplication. May not be
846            // worth it.
847            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                        // Clients sometimes hang up during the startup sequence, e.g.
854                        // because they receive an unacceptable response to an
855                        // `SslRequest`. This is considered a graceful termination.
856                        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                            // Do not wait on cancel requests to return because cancellation is best
927                            // effort.
928                            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
963// A struct that counts bytes exchanged.
964struct 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
1038/// Broadcasts cancellation to all matching environmentds. `conn_id`'s bits [31..20] are the lower
1039/// 12 bits of a UUID for an environmentd/organization. Using that and the template in
1040/// `cancellation_resolver` we generate a hostname. That hostname resolves to all IPs of envds that
1041/// match the UUID (cloud k8s infrastructure maintains that mapping). This function creates a new
1042/// task for each envd and relays the cancellation message to it, broadcasting it to any envd that
1043/// might match the connection.
1044///
1045/// This function returns after it has spawned the tasks, and does not wait for them to complete.
1046/// This is acceptable because cancellation in the Postgres protocol is best effort and has no
1047/// guarantees.
1048///
1049/// The safety of broadcasting this is due to the various randomness in the connection id and secret
1050/// key, which must match exactly in order to execute a query cancellation. The connection id has 19
1051/// bits of randomness, and the secret key the full 32, for a total of 51 bits. That is more than
1052/// 2e15 combinations, enough to nearly certainly prevent two different envds generating identical
1053/// combinations.
1054async 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                // Without SNI, resolve the template as is. Not expected for
1131                // HTTPS in practice.
1132                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
1145/// Extracts the tenant ID from an environmentd CNAME target.
1146///
1147/// The CNAME points at the environmentd service, of the form
1148/// `<service>.<namespace>.svc.cluster.local`, e.g.
1149/// `environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local`.
1150/// The `<namespace>` is the environment name `environment-<tenant_id>-<index>`,
1151/// where `<tenant_id>` is the tenant's UUID and `<index>` is the environment
1152/// generation.
1153///
1154/// NOTE: `<index>` is currently always 0, since a tenant has one environment
1155/// per region, but this does not rely on that so that multiple environments
1156/// per tenant can be supported later.
1157fn 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    // Trim off the starting `environment-`.
1164    let Some((_, namespace)) = namespace.split_once('-') else {
1165        return None;
1166    };
1167    // Trim off the ending `-<index>`.
1168    let Some((tenant, _)) = namespace.rsplit_once('-') else {
1169        return None;
1170    };
1171    // Convert to a Uuid so that this tenant matches the frontegg resolver exactly, because it
1172    // also uses Uuid::to_string.
1173    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
1180/// Strips the surrounding brackets from an IPv6 host literal, e.g. `[::1]`
1181/// becomes `::1`. Leaves other hosts unchanged. This lets a bracketed IPv6
1182/// literal in an address template parse as an `IpAddr` and resolve, matching
1183/// what `tokio::net::lookup_host` accepts.
1184fn 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    // TODO(jkosh44) consider forwarding the connection UUID to the adapter.
1194    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                        // We know this is an HTTPs stream (see name
1247                        // HttpsBalancer), but we actually don't care what type
1248                        // of traffic it is and we only use raw tcp streams.In
1249                        // order to respond with HTTP we have to write this as a
1250                        // raw http message.
1251                        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                    // Write the tcp proxy header
1269                    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                    // do a TLS handshake
1278                    let mut builder =
1279                        SslConnector::builder(SslMethod::tls()).expect("Error creating builder.");
1280                    // environmentd doesn't yet have a cert we trust, so for now disable verification.
1281                    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                // Now blindly shuffle bytes back and forth until closed.
1295                // TODO: Limit total memory use.
1296                // See corresponding comment in pgwire implementation about ignoring the error.
1297                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/// Template for constructing the destination hostname from a TLS SNI
1321/// servername. `{}` is replaced with the first label of the servername.
1322#[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/// An error resolving a connection's destination.
1342///
1343/// The `Display` of this error is sent to unauthenticated clients, so it must
1344/// not contain internal details such as hostnames. Those belong in the source
1345/// error attached to each variant, which is only logged.
1346#[derive(Debug, thiserror::Error)]
1347enum ResolveError {
1348    #[error("invalid password")]
1349    InvalidPassword,
1350    /// A client protocol violation, e.g. sending the wrong message during
1351    /// startup. An unauthenticated client can trigger these at will, so they
1352    /// are logged at `warn!`, not `error!`, to avoid making client noise look
1353    /// like server faults and spamming the error log.
1354    #[error("internal error")]
1355    Client(#[source] anyhow::Error),
1356    /// The tenant's upstream backend could not be reached, e.g. its hostname
1357    /// did not resolve. Reported to the client with a distinct, non-leaking
1358    /// message rather than a generic internal error, so a down environment is
1359    /// not mistaken for a balancerd bug. Logged at `warn!`: a bogus SNI reaches
1360    /// this from an unauthenticated client, and a genuinely down environment is
1361    /// an operational condition, not a balancerd fault.
1362    #[error("upstream server not available")]
1363    Upstream(#[source] anyhow::Error),
1364    /// A server-side fault.
1365    #[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    /// Returns a clone of the shared DNS resolver if in multi-tenant mode.
1377    /// This allows sharing the resolver with other components like HttpsBalancer.
1378    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                        // A resolution failure here means the tenant's backend
1419                        // is unreachable (or the client sent a bogus SNI). Not
1420                        // a server fault.
1421                        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                        // balancerd only needs the validated tenant_id to route
1446                        // the connection; group extraction happens in
1447                        // environmentd, so skip it here.
1448                        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                        // The tenant is already known from authentication, so
1466                        // skip the CNAME lookup that resolve() would do. A
1467                        // failure here means the tenant's backend is unreachable.
1468                        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                // We don't want any caching here so we just use the standard
1492                // tokio resolver.
1493                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
1507/// Creates a resolver from the system DNS configuration.
1508///
1509/// Caching is delegated to the infrastructure (node-local DNS), so this
1510/// resolver does no caching of its own. Fails if the system DNS configuration
1511/// cannot be read. We must not fall back to hickory's default config (Google
1512/// public DNS) here, that would leak internal hostnames to an external party
1513/// and could not resolve them anyway.
1514fn 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    // Query A records first and AAAA only on failure, rather than both.
1518    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/// Resolves tenant hostnames for pgwire and HTTPS routing.
1528///
1529/// Caching is delegated to the infrastructure (node-local DNS), so every
1530/// lookup issues a query. CNAMEs are resolved separately from A records only
1531/// because the CNAME carries the tenant, not for caching reasons.
1532#[derive(Debug)]
1533pub struct TenantDnsResolver {
1534    resolver: TokioResolver,
1535}
1536
1537impl TenantDnsResolver {
1538    /// Creates a new resolver. Fails if the system DNS configuration cannot be
1539    /// read.
1540    pub fn new() -> Result<Self, anyhow::Error> {
1541        Ok(Self {
1542            resolver: create_resolver()?,
1543        })
1544    }
1545
1546    /// Resolves the CNAME a hostname points at, if any.
1547    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    /// Resolves the A records for a hostname.
1567    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    /// Resolves the environment address for a TLS SNI servername.
1575    ///
1576    /// `servername` is the first label of the SNI host, e.g.
1577    /// `3dl07g8zmj91pntk4eo9cfvwe`. Substituting it into `template` (e.g.
1578    /// `blncr-{}`) yields a Kubernetes hostname like
1579    /// `blncr-3dl07g8zmj91pntk4eo9cfvwe`, which resolves via a CNAME to the
1580    /// environmentd service, e.g.
1581    /// `environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local`.
1582    /// The tenant is extracted from that CNAME. See
1583    /// `extract_tenant_from_cname`.
1584    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    /// Resolves the address for a hostname, skipping CNAME resolution and
1596    /// tenant extraction. Use when the tenant is already known.
1597    async fn resolve_addr(&self, host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
1598        let host = strip_ipv6_brackets(host);
1599        // IP literals need no resolution. Resolving them through hickory
1600        // would walk the search domain list first when ndots is large, as it
1601        // is in Kubernetes.
1602        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    /// Resolves the address and tenant from a hostname and port.
1609    ///
1610    /// The tenant is extracted from the CNAME if the hostname points at one.
1611    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        // IP literals need no resolution and carry no tenant CNAME.
1618        if let Ok(ip) = host.parse::<IpAddr>() {
1619            return Ok((SocketAddr::new(ip, port), None));
1620        }
1621
1622        // The CNAME carries the tenant, so resolve it separately to extract it.
1623        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    /// Returns the first resolved IP as a socket address.
1634    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                // Trailing dot from an absolute DNS name, as returned by the
1669                // resolver.
1670                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local.",
1671                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1672            ),
1673            (
1674                // Variously named parts.
1675                "service.something-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.ssvvcc.cloister.faraway",
1676                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1677            ),
1678            (
1679                // No dashes in uuid.
1680                "environmentd.environment-58cd23ffa4d74bd0ad85a6ff29cc86c3-0.svc.cluster.local",
1681                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1682            ),
1683            (
1684                // -1234 suffix.
1685                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-1234.svc.cluster.local",
1686                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1687            ),
1688            (
1689                // Uppercase.
1690                "environmentd.environment-58CD23FF-A4D7-4BD0-AD85-A6FF29CC86C3-0.svc.cluster.local",
1691                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
1692            ),
1693            (
1694                // No -number suffix.
1695                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3.svc.cluster.local",
1696                None,
1697            ),
1698            (
1699                // No service name.
1700                "environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
1701                None,
1702            ),
1703            (
1704                // Invalid UUID.
1705                "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        // Hosts without a matched bracket pair are left untouched.
1724        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}