Skip to main content

sozu_lib/
https.rs

1//! HTTPS proxy entry point.
2//!
3//! Owns the TLS listener config (rustls), the ALPN-driven post-handshake
4//! mux dispatch (`h2` → `ConnectionH2`, `http/1.1` → `ConnectionH1`,
5//! neither → reject + `https.alpn.rejected.{unsupported,http11_disabled}`
6//! metrics), the SNI binding policy (`strict_sni_binding`), and the
7//! listener-update surface called from the command socket. Front-end H2
8//! is gated by ALPN here; `cluster.http2` is a backend-capability hint.
9//! Frontend rustls handshake I/O lives in `lib/src/protocol/rustls.rs`;
10//! certificate resolution lives in `lib/src/tls.rs`.
11
12use std::{
13    cell::RefCell,
14    collections::{BTreeMap, HashMap, hash_map::Entry},
15    io::ErrorKind,
16    net::SocketAddr as StdSocketAddr,
17    rc::{Rc, Weak},
18    str::{from_utf8, from_utf8_unchecked},
19    sync::Arc,
20    time::{Duration, Instant},
21};
22
23use mio::{
24    Interest, Registry, Token,
25    net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream},
26};
27use rustls::{
28    CipherSuite, ProtocolVersion, RootCertStore, ServerConfig as RustlsServerConfig,
29    ServerConnection, SupportedCipherSuite,
30    crypto::CryptoProvider,
31    pki_types::{CertificateDer, CertificateRevocationListDer, pem::PemObject},
32    server::WebPkiClientVerifier,
33};
34use rusty_ulid::Ulid;
35use socket2::SockRef;
36use sozu_command::{
37    certificate::Fingerprint,
38    config::{DEFAULT_ALPN_PROTOCOLS, DEFAULT_CIPHER_LIST},
39    listener_key::ListenerKey,
40    proto::command::{
41        AddCertificate, CertificateSummary, CertificatesByAddress, ClientAuthMode,
42        ClientAuthPolicy, Cluster, ForwardedHeaders, HttpsListenerConfig,
43        ListOfCertificatesByAddress, ListenerType, RemoveCertificate, RemoveListener,
44        ReplaceCertificate, RequestHttpFrontend, ResponseContent, TlsVersion,
45        UpdateHttpsListenerConfig, WorkerRequest, WorkerResponse, request::RequestType,
46        response_content::ContentType,
47    },
48    ready::Ready,
49    response::HttpFrontend,
50    state::{
51        ClusterId, validate_alpn_protocols, validate_forwarded_headers,
52        validate_h2_flood_knobs_https, validate_sozu_id_header,
53    },
54};
55
56use crate::metrics::names;
57use crate::{
58    AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError,
59    ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed,
60    SessionMetrics, SessionResult, StateMachineBuilder, StateResult,
61    backends::BackendMap,
62    crypto::{cipher_suite_by_name, default_provider, kx_group_by_name},
63    pool::Pool,
64    protocol::{
65        Pipe, SessionState,
66        http::answers::HttpAnswers,
67        http::parser::{Method, hostname_and_port},
68        mux::{self, Mux, MuxTls},
69        proxy_protocol::expect::ExpectProxyProtocol,
70        rustls::TlsHandshake,
71    },
72    router::{RequestHeaders, RouteResult, Router},
73    server::{ListenToken, SessionManager},
74    socket::{FrontRustls, RecvMemory, server_bind},
75    timer::TimeoutContainer,
76    tls::MutexCertificateResolver,
77};
78
79StateMachineBuilder! {
80    /// The various Stages of an HTTPS connection:
81    ///
82    /// - optional (ExpectProxyProtocol)
83    /// - TLS handshake
84    /// - HTTP or HTTP2 (via Mux)
85    /// - WebSocket (passthrough), only from HTTP/1.1
86    enum HttpsStateMachine impl SessionState {
87        Expect(ExpectProxyProtocol<MioTcpStream>, ServerConnection),
88        Handshake(TlsHandshake),
89        Mux(MuxTls),
90        WebSocket(Pipe<FrontRustls, HttpsListener>),
91    }
92}
93
94enum AlpnProtocol {
95    H2,
96    Http11,
97}
98
99/// Monotonic rank of an HTTPS lifecycle stage, used only by `debug_assert!`s to
100/// check that upgrades move strictly forward (Expect → Handshake → Mux →
101/// WebSocket) and never re-enter an earlier stage. Gated to debug builds so it
102/// does not register as dead code in release.
103#[cfg(debug_assertions)]
104fn https_stage_rank(marker: StateMarker) -> u8 {
105    match marker {
106        StateMarker::Expect => 0,
107        StateMarker::Handshake => 1,
108        StateMarker::Mux => 2,
109        StateMarker::WebSocket => 3,
110    }
111}
112
113/// Module-level prefix for log lines emitted from this file when no session
114/// is in scope. Produces a bold bright-white `HTTPS` label in colored mode.
115/// Used by [`HttpsProxy`] / [`HttpsListener`] callbacks (`notify`,
116/// `add_cluster`, `add_*_frontend`, `accept`, `soft_stop`, `hard_stop`)
117/// which own a token map keyed by listener and have no `frontend_token` of
118/// their own.
119macro_rules! log_module_context {
120    () => {{
121        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
122        format!("{open}HTTPS{reset}\t >>>", open = open, reset = reset)
123    }};
124}
125
126/// Per-session prefix for log lines emitted with an [`HttpsSession`] in
127/// scope. Renders the canonical `\tHTTPS\tSession(...)\t >>>` envelope from
128/// the session's `frontend_token` and `peer_address`. Operators can grep-
129/// correlate against the token id (and the peer address when present)
130/// across log lines for the same TLS connection.
131macro_rules! log_context {
132    ($self:expr) => {{
133        let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
134        format!(
135            "{open}HTTPS{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}peer{reset}={white}{peer}{reset})\t >>>",
136            open = open,
137            reset = reset,
138            grey = grey,
139            gray = gray,
140            white = white,
141            frontend = $self.frontend_token.0,
142            peer = $self.peer_address.map(|a| a.to_string()).unwrap_or_else(|| "<none>".to_string()),
143        )
144    }};
145}
146
147fn successful_tls_handshake_summary(sni: Option<&str>, alpn: Option<&str>) -> String {
148    format!(
149        "successful TLS handshake (sni_bytes={:?}, alpn_bytes={:?})",
150        sni.map(str::len),
151        alpn.map(str::len),
152    )
153}
154
155/// The readiness events the upgraded mux frontend starts with.
156///
157/// WRITABLE always: the H2 server must send its SETTINGS whatever the peer
158/// does next, and an upgrade from `TlsHandshake::writable` has no fresh event.
159///
160/// READABLE only when a read is due without a new edge:
161///
162/// - `handshake_event` still carries READABLE. `handshake_read`
163///   (`lib/src/protocol/rustls.rs`) drops it exactly when a `recv` proved the
164///   receive queue empty — EAGAIN or a short read — and any segment that
165///   arrived since raised an edge that `TlsHandshake::update_readiness` put
166///   back;
167/// - `rustls_holds_input`: `ServerConnection::wants_read()` is false, so
168///   rustls already decrypted plaintext — the HTTP/2 preface or an HTTP/1.1
169///   request that shared a segment with the client `Finished` — or received
170///   `close_notify`. Neither will raise an edge, and the mux read serves both
171///   without a `recv`.
172///
173/// Anything else means the socket was drained after the last edge, and bytes
174/// arriving later raise their own event (mio registers edge-triggered, see
175/// `plain_socket_read` in `lib/src/socket.rs`), so an unconditional READABLE
176/// only bought a `recv` that answered EAGAIN on every TLS connection.
177///
178/// HUP and WRITE_CLOSED survive the upgrade: their edge does not fire again,
179/// and WRITE_CLOSED is what tells the mux a hang-up from a half-close. A client that
180/// half-closed with its request (`TlsHandshake::ready` in
181/// `lib/src/protocol/rustls.rs` reads past that HUP) is served by the mux,
182/// which closes the session on the HUP once the response is complete
183/// (`Mux::ready_inner`). Dropping it left that session open after its
184/// response until the frontend timeout.
185fn upgraded_frontend_events(handshake_event: Ready, rustls_holds_input: bool) -> Ready {
186    // WRITE_CLOSED too: it is what tells a hang-up from a half-close.
187    let hup = handshake_event & (Ready::HUP | Ready::WRITE_CLOSED);
188    if handshake_event.is_readable() || rustls_holds_input {
189        Ready::READABLE | Ready::WRITABLE | hup
190    } else {
191        Ready::WRITABLE | hup
192    }
193}
194
195/// Render an SNI trie key as the `domain` of a [`CertificateSummary`].
196///
197/// Unreachable in practice: every key enters the trie as
198/// `String::into_bytes()` (`tls.rs` `add_certificate`/`remove_certificate`),
199/// so it is always valid UTF-8. `TrieNode`'s key type is `Vec<u8>` though, and
200/// a read-only certificate listing must degrade rather than abort the worker.
201///
202/// The degradation is not silent: `CertificateSummary::domain` is a protobuf
203/// `string`, so a non-UTF-8 name has no faithful rendering, and the U+FFFD
204/// substitutions mean the name handed to the operator will NOT match a later
205/// `RemoveCertificate` for that certificate. That is worth an `error!` rather
206/// than a quietly mangled row. The log carries byte counts only — never the
207/// key — to stay inside the redaction bound the `query_*` log tests assert.
208fn certificate_summary_domain(key: &[u8]) -> String {
209    match from_utf8(key) {
210        Ok(domain) => domain.to_owned(),
211        Err(error) => {
212            error!(
213                "{} certificate domain is not valid UTF-8; the reported name is lossy and will not \
214                 match a later RemoveCertificate, domain_bytes={} valid_up_to={}",
215                log_module_context!(),
216                key.len(),
217                error.valid_up_to(),
218            );
219            String::from_utf8_lossy(key).into_owned()
220        }
221    }
222}
223
224pub struct HttpsSession {
225    configured_backend_timeout: Duration,
226    configured_connect_timeout: Duration,
227    configured_frontend_timeout: Duration,
228    frontend_token: Token,
229    has_been_closed: bool,
230    last_event: Instant,
231    listener: Rc<RefCell<HttpsListener>>,
232    metrics: SessionMetrics,
233    peer_address: Option<StdSocketAddr>,
234    pool: Weak<RefCell<Pool>>,
235    proxy: Rc<RefCell<HttpsProxy>>,
236    public_address: StdSocketAddr,
237    state: HttpsStateMachine,
238}
239
240impl HttpsSession {
241    #[allow(clippy::too_many_arguments)]
242    pub fn new(
243        configured_backend_timeout: Duration,
244        configured_connect_timeout: Duration,
245        configured_frontend_timeout: Duration,
246        configured_request_timeout: Duration,
247        expect_proxy: bool,
248        listener: Rc<RefCell<HttpsListener>>,
249        pool: Weak<RefCell<Pool>>,
250        proxy: Rc<RefCell<HttpsProxy>>,
251        public_address: StdSocketAddr,
252        rustls_details: ServerConnection,
253        sock: MioTcpStream,
254        peer: StdSocketAddr,
255        token: Token,
256        wait_time: Duration,
257    ) -> HttpsSession {
258        // Timeouts are wired from the listener config and feed `TimeoutContainer`s
259        // that arm the event loop. A zero request timeout would arm a deadline that
260        // fires on the very next tick, so reaching this constructor with one signals
261        // a config-loading bug upstream rather than hostile input.
262        debug_assert!(
263            !configured_request_timeout.is_zero(),
264            "HTTPS session request timeout must be non-zero (would arm an immediate deadline)"
265        );
266        debug_assert!(
267            !configured_frontend_timeout.is_zero() && !configured_backend_timeout.is_zero(),
268            "HTTPS session front/back timeouts must be non-zero"
269        );
270
271        let peer_address = if expect_proxy {
272            // Will be defined later once the expect proxy header has been received and parsed
273            None
274        } else {
275            // The address `accept(2)` returned with `sock`.
276            Some(peer)
277        };
278
279        let request_id = Ulid::generate();
280        let container_frontend_timeout = TimeoutContainer::new(configured_request_timeout, token);
281
282        let state = if expect_proxy {
283            trace!("{} starting in expect proxy state", log_module_context!());
284            gauge_add!(names::protocol::PROXY_EXPECT, 1);
285            HttpsStateMachine::Expect(
286                ExpectProxyProtocol::new(container_frontend_timeout, sock, token, request_id),
287                rustls_details,
288            )
289        } else {
290            gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
291            HttpsStateMachine::Handshake(TlsHandshake::new(
292                container_frontend_timeout,
293                rustls_details,
294                sock,
295                token,
296                request_id,
297                peer_address,
298            ))
299        };
300
301        // The freshly built state must reflect the entry-protocol choice exactly:
302        // `expect_proxy` enters via PROXY-protocol parsing, otherwise straight into
303        // the TLS handshake. No other entry state is legal, and `peer_address` is
304        // unknown until the PROXY header is parsed (mirror of the `if` above).
305        debug_assert_eq!(
306            matches!(state, HttpsStateMachine::Expect(..)),
307            expect_proxy,
308            "fresh HTTPS session must start in Expect iff expect_proxy is set"
309        );
310        debug_assert!(
311            expect_proxy || matches!(state, HttpsStateMachine::Handshake(_)),
312            "non-expect-proxy HTTPS session must start in the TLS Handshake state"
313        );
314        debug_assert!(
315            !expect_proxy || peer_address.is_none(),
316            "expect-proxy peer address is only known after the PROXY header is parsed"
317        );
318
319        let metrics = SessionMetrics::new(Some(wait_time));
320        HttpsSession {
321            configured_backend_timeout,
322            configured_connect_timeout,
323            configured_frontend_timeout,
324            frontend_token: token,
325            has_been_closed: false,
326            last_event: Instant::now(),
327            listener,
328            metrics,
329            peer_address,
330            pool,
331            proxy,
332            public_address,
333            state,
334        }
335    }
336
337    pub fn upgrade(&mut self) -> SessionIsToBeClosed {
338        debug!("{} upgrade", log_context!(self));
339        // `take()` swaps in a FailedUpgrade carrying the marker of the state we
340        // are leaving, so the marker observed here is the *origin* of this
341        // upgrade. Capture it to check the transition is forward-only below.
342        // Read only by debug-only asserts → cfg-gated so release has no unused
343        // binding.
344        #[cfg(debug_assertions)]
345        let from_marker = self.state.marker();
346        let new_state = match self.state.take() {
347            HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl),
348            HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake),
349            HttpsStateMachine::Mux(mux) => self.upgrade_mux(mux),
350            HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss),
351            HttpsStateMachine::FailedUpgrade(_) => {
352                // Reaching this arm means a prior upgrade already returned
353                // `None` and the session should have been closed. Fall back
354                // to closing cleanly instead of panicking the worker.
355                error!(
356                    "{} upgrade called on FailedUpgrade state; closing session",
357                    log_context!(self)
358                );
359                None
360            }
361        };
362
363        match new_state {
364            Some(state) => {
365                // The HTTPS lifecycle is strictly forward: Expect → Handshake →
366                // Mux → WebSocket. A successful upgrade must move to a strictly
367                // later stage and never re-enter the one it came from (no
368                // re-handshake mid-stream, no fall-back to Expect). `WebSocket`
369                // is terminal: `upgrade_websocket` returns the same state, so we
370                // exempt the self-loop there. The whole assert is cfg-gated (not
371                // just `debug_assert!`'s runtime guard) because its argument calls
372                // the debug-only `https_stage_rank`; leaving the call to compile
373                // in release would be an E0425 (HARD RULE #2).
374                #[cfg(debug_assertions)]
375                debug_assert!(
376                    https_stage_rank(state.marker()) > https_stage_rank(from_marker)
377                        || matches!(from_marker, StateMarker::WebSocket),
378                    "HTTPS upgrade must advance the lifecycle (from {:?} to {:?})",
379                    from_marker,
380                    state.marker()
381                );
382                debug_assert!(
383                    !state.failed(),
384                    "a successful HTTPS upgrade must not yield a FailedUpgrade state"
385                );
386                self.state = state;
387                false
388            }
389            // The state stays FailedUpgrade, but the Session should be closed right after
390            None => {
391                // On a refused upgrade `take()` left a FailedUpgrade behind that
392                // still remembers the origin stage, so `close()` can restore the
393                // right gauge. Guard that the marker survived the failed attempt.
394                debug_assert!(
395                    self.state.failed(),
396                    "a refused HTTPS upgrade must leave the state in FailedUpgrade"
397                );
398                // cfg-gated for the same E0425 reason as the success arm above.
399                #[cfg(debug_assertions)]
400                debug_assert!(
401                    https_stage_rank(self.state.marker()) == https_stage_rank(from_marker),
402                    "FailedUpgrade must retain the origin stage marker for gauge restoration"
403                );
404                true
405            }
406        }
407    }
408
409    fn upgrade_expect(
410        &mut self,
411        mut expect: ExpectProxyProtocol<MioTcpStream>,
412        ssl: ServerConnection,
413    ) -> Option<HttpsStateMachine> {
414        if let Some(ref addresses) = expect.addresses
415            && let (Some(public_address), Some(session_address)) =
416                (addresses.destination(), addresses.source())
417        {
418            self.public_address = public_address;
419            self.peer_address = Some(session_address);
420
421            let ExpectProxyProtocol {
422                container_frontend_timeout,
423                frontend,
424                frontend_readiness: readiness,
425                request_id,
426                ..
427            } = expect;
428
429            let mut handshake = TlsHandshake::new(
430                container_frontend_timeout,
431                ssl,
432                frontend,
433                self.frontend_token,
434                request_id,
435                self.peer_address,
436            );
437            // Transfer both interest and event from the proxy protocol state,
438            // so the event loop properly monitors the socket after the transition.
439            handshake.frontend_readiness = readiness;
440            handshake.frontend_readiness.event.insert(Ready::READABLE);
441
442            // The PROXY header just resolved both endpoints; the session now
443            // knows its true peer, and the handshake must watch for readable
444            // bytes or the TLS ClientHello will never be serviced.
445            debug_assert_eq!(
446                self.peer_address,
447                Some(session_address),
448                "expect upgrade must adopt the PROXY-advertised source as the peer address"
449            );
450            debug_assert!(
451                handshake.frontend_readiness.event.is_readable(),
452                "handshake handed off from expect must be armed for READABLE"
453            );
454
455            gauge_add!(names::protocol::PROXY_EXPECT, -1);
456            gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
457            return Some(HttpsStateMachine::Handshake(handshake));
458        }
459
460        // currently, only happens in expect proxy protocol with AF_UNSPEC address
461        if !expect.container_frontend_timeout.cancel() {
462            error!(
463                "{} failed to cancel request timeout on expect upgrade phase for 'expect proxy protocol with AF_UNSPEC address'",
464                log_context!(self)
465            );
466        }
467
468        None
469    }
470
471    fn upgrade_handshake(&mut self, handshake: TlsHandshake) -> Option<HttpsStateMachine> {
472        // Capture the SNI as an owned, already-lowercased String so it outlives
473        // the `handshake.session` move below. Lowercasing here once avoids
474        // doing it on every route decision (RFC 9110 §4.2.3 says hostnames are
475        // case-insensitive); no port is ever part of an SNI value (RFC 6066
476        // §3 — `HostName` is a dns_name, no port).
477        // RFC 1034 §3.1 absolute-form: `example.com.` and `example.com`
478        // are the same host. rustls hands us the wire-form SNI verbatim;
479        // strip a single trailing dot so a legitimate client emitting
480        // absolute-form SNI does not get its
481        // `host` / `:authority` rejected by `authority_matches_sni` for a
482        // length mismatch. Empty / no-SNI is unaffected.
483        let sni_owned: Option<String> = handshake
484            .session
485            .server_name()
486            .map(|s| s.to_ascii_lowercase())
487            .map(|mut s| {
488                if s.ends_with('.') {
489                    s.pop();
490                }
491                s
492            });
493        let alpn = handshake.session.alpn_protocol();
494        let alpn = alpn.and_then(|alpn| from_utf8(alpn).ok());
495        debug!(
496            "{} {}",
497            log_context!(self),
498            successful_tls_handshake_summary(sni_owned.as_deref(), alpn)
499        );
500
501        // Reject clients that fail to negotiate `h2` when the listener is
502        // configured as H2-only: silently falling back to HTTP/1.1 would let a
503        // downgrade-capable peer bypass H2-specific protections advertised
504        // for this listener (Pass 5 Medium #4 of the security audit).
505        let disable_http11 = self.listener.borrow().is_http11_disabled();
506        // Pair the parsed AlpnProtocol with the on-the-wire label so the
507        // access log can record it as a `&'static str` without re-stringifying
508        // the protocol enum on every request. Unknown ALPN values still bail
509        // out below — only successful negotiations propagate to the log.
510        let (alpn, alpn_label): (AlpnProtocol, Option<&'static str>) = match alpn {
511            Some("http/1.1") => {
512                if disable_http11 {
513                    incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
514                    warn!(
515                        "{} rejecting TLS connection: listener is H2-only but client negotiated http/1.1",
516                        log_context!(self)
517                    );
518                    return None;
519                }
520                (AlpnProtocol::Http11, Some("http/1.1"))
521            }
522            Some("h2") => (AlpnProtocol::H2, Some("h2")),
523            Some(other) => {
524                // This branch was not metered, so any operator dashboard
525                // graphing `https.alpn.rejected.*`
526                // missed unknown-protocol refusals (e.g. an `h3` mistake
527                // bleeding through some misconfiguration). Add a dedicated
528                // counter so the SOC's "ALPN refusal" ratebar matches the
529                // sum of the labelled buckets.
530                incr!(names::https::ALPN_REJECTED_UNSUPPORTED);
531                error!(
532                    "{} unsupported ALPN protocol: alpn_bytes={}",
533                    log_context!(self),
534                    other.len()
535                );
536                return None;
537            }
538            // Some clients don't fill in the ALPN protocol. By default we
539            // downgrade to HTTP/1.1 to preserve compatibility; on an H2-only
540            // listener we instead drop the connection.
541            None => {
542                if disable_http11 {
543                    incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
544                    warn!(
545                        "{} rejecting TLS connection: listener is H2-only but client did not negotiate ALPN",
546                        log_context!(self)
547                    );
548                    return None;
549                }
550                (AlpnProtocol::Http11, None)
551            }
552        };
553
554        // Post-decision invariant: every refusal path above already returned, so
555        // reaching here means the negotiated protocol is one Sōzu serves. An
556        // H2-only listener must therefore have landed on H2 — never on the H1
557        // dispatch — or the `disable_http11` guard leaked a downgrade. The label,
558        // when present, must name exactly the protocol we are about to build.
559        debug_assert!(
560            !disable_http11 || matches!(alpn, AlpnProtocol::H2),
561            "H2-only listener must not dispatch an HTTP/1.1 session past ALPN"
562        );
563        debug_assert!(
564            match (&alpn, alpn_label) {
565                (AlpnProtocol::H2, Some(l)) => l == "h2",
566                (AlpnProtocol::Http11, Some(l)) => l == "http/1.1",
567                // Absent label only for ALPN-less HTTP/1.1 downgrade.
568                (AlpnProtocol::Http11, None) => true,
569                (AlpnProtocol::H2, None) => false,
570            },
571            "negotiated ALPN protocol and its wire label must agree"
572        );
573
574        // Capture the negotiated TLS metadata as `&'static str` labels for the
575        // access log alongside the existing metric counters. Both calls are
576        // single rustls accessors — duplicating them keeps the metric path
577        // unchanged and avoids mutating-after-move on `handshake.session`.
578        let tls_version_label = handshake
579            .session
580            .protocol_version()
581            .and_then(rustls_version_label);
582        let tls_cipher_label = handshake
583            .session
584            .negotiated_cipher_suite()
585            .and_then(rustls_ciphersuite_label);
586        if let Some(version) = handshake.session.protocol_version() {
587            incr!(rustls_version_str(version));
588        };
589        if let Some(cipher) = handshake.session.negotiated_cipher_suite() {
590            incr!(rustls_ciphersuite_str(cipher));
591        };
592
593        gauge_add!(names::protocol::TLS_HANDSHAKE, -1);
594
595        // Read before `handshake.session` moves into `FrontRustls` below.
596        let frontend_events = upgraded_frontend_events(
597            handshake.frontend_readiness.event,
598            !handshake.session.wants_read(),
599        );
600        // No READABLE left in the handshake's event: its last `recv` proved
601        // the socket empty and no data event was delivered since, so the
602        // first mux read serves what rustls holds without a `recv`.
603        let recv_memory =
604            RecvMemory::after_handshake(!handshake.frontend_readiness.event.is_readable());
605
606        let session_ulid = rusty_ulid::Ulid::generate();
607        let front_stream = FrontRustls {
608            stream: handshake.stream,
609            session: handshake.session,
610            peer_disconnected: false,
611            peer_reset: false,
612            tls_fatal: false,
613            session_ulid,
614            // `self.peer_address`, NOT `stream.peer_addr()`. On an expect-proxy
615            // frontend `upgrade_expect` has already adopted the PROXY-advertised
616            // source here, while the socket's own peer is the load balancer: the
617            // raw lookup compiles, passes, and silently makes the `MUX-*` lines
618            // name a different host than the `HTTPS` line for the same request
619            // id. This is the same value handed to `Context::new` below, so the
620            // two halves of the session agree by construction.
621            configured_peer: self.peer_address,
622            recv_memory,
623        };
624        let router = mux::Router::new(
625            self.configured_backend_timeout,
626            self.configured_connect_timeout,
627        );
628        let mut context = mux::Context::new(
629            session_ulid,
630            self.pool.clone(),
631            self.listener.clone(),
632            self.peer_address,
633            self.public_address,
634        );
635        // Snapshot the SAN set of the certificate this handshake actually
636        // served. Frozen at handshake to match browser behaviour (Firefox
637        // and Chrome cache the validated cert per connection — RFC 7540
638        // §9.1.1 / RFC 9113 §9.1.1) and so the H2 router can accept
639        // coalesced streams whose `:authority` is covered by any SAN
640        // (RFC 6125 §6.4.3 wildcards).
641        //
642        // # Known race window (accepted risk)
643        //
644        // This is a SECOND lookup, separate from rustls's `resolve()`
645        // callback. Between rustls's `resolve()` (during ClientHello
646        // processing) and this block (post-`Finished`), the mio loop may
647        // dispatch the command-channel token — handlers there call
648        // `add_certificate` / `remove_certificate`, which mutate the same
649        // resolver trie. The single-threaded-worker invariant prevents
650        // simultaneous mutation, but not interleaving between mio
651        // iterations.
652        //
653        // Realistic threat model: internal misuse — a tenant operator
654        // with config-IPC privilege races a `remove_certificate(A)` plus
655        // `add_certificate(B covering same SNI)` inside the handshake
656        // window. The snapshot below then reflects B instead of A. The
657        // attacker already holds the trust boundary they would need to
658        // mint a malicious cert outright (config IPC == resolver write
659        // privilege), so the race grants no privilege the attacker did
660        // not already have. Closing it structurally would require either
661        // (a) rustls API support to recover the served cert chain
662        // post-handshake (`server_cert_chain` is `pub(crate)` in rustls
663        // 0.23.x) or (b) threading per-session state from `resolve()` to
664        // here through a side-channel that handles out-of-order handshake
665        // completion — both deferred. Keep the second lookup; document
666        // the window honestly.
667        //
668        // Cases handled:
669        //   * SNI absent → `None`; routing falls back to the legacy
670        //     `authority_matches_sni` predicate (no SNI ⇒ predicate no-ops).
671        //   * SNI present and the resolver returned a SAN-bearing cert →
672        //     `Some(snapshot)` (lowercase + trailing-dot strip + dedup).
673        //     Routing accepts `:authority` covered by the SAN set with
674        //     RFC 6125 §6.4.3 wildcard handling — this is the H2
675        //     connection-coalescing fix (RFC 7540 §9.1.1 / RFC 9113
676        //     §9.1.1, Firefox + Chrome semantics).
677        //   * SNI present but no matching cert (rustls served the default
678        //     cert) → `None`. The legacy exact-match fallback applies:
679        //     accept iff `:authority == SNI`, identical to the pre-fix
680        //     behaviour. Returning `Some(empty)` here would block every
681        //     authority — including configurations where the operator
682        //     intentionally keeps a frontend reachable on a different cert
683        //     (test fixtures, dev setups, misconfigured listeners). The
684        //     real defence stays on the client: a browser will refuse the
685        //     default cert when SNI doesn't validate against it; a
686        //     deliberate insecure client choosing to ignore that is
687        //     responsible for its own behaviour and is not a trust-boundary
688        //     concern for the proxy.
689        let tls_cert_names: Option<Arc<Vec<String>>> = match sni_owned.as_deref() {
690            Some(sni) => self
691                .listener
692                .borrow()
693                .resolver()
694                .names_for_sni(sni.as_bytes())
695                .and_then(|names| {
696                    let mut snapshot: Vec<String> = names
697                        .into_iter()
698                        .map(|mut name| {
699                            name.make_ascii_lowercase();
700                            if name.ends_with('.') {
701                                name.pop();
702                            }
703                            name
704                        })
705                        .collect();
706                    snapshot.sort();
707                    snapshot.dedup();
708                    if snapshot.is_empty() {
709                        None
710                    } else {
711                        Some(Arc::new(snapshot))
712                    }
713                }),
714            None => None,
715        };
716        // Structural postcondition of the SAN-snapshot builder above: a cert-name
717        // snapshot only exists when the client sent an SNI (the `None => None`
718        // arm), and when present it is non-empty (empty collapses to `None`) and
719        // sorted+deduped (so the H2 router's coalescing check sees a canonical
720        // set). These hold regardless of `strict_sni_binding`; the binding policy
721        // is *enforced* later at routing, but the snapshot feeding it must be
722        // well-formed here.
723        debug_assert!(
724            tls_cert_names.is_none() || sni_owned.is_some(),
725            "cert-name snapshot must not exist without an SNI to key it"
726        );
727        debug_assert!(
728            tls_cert_names
729                .as_ref()
730                .is_none_or(|names| { !names.is_empty() && names.windows(2).all(|w| w[0] < w[1]) }),
731            "cert-name snapshot must be non-empty and strictly sorted (sorted + deduped)"
732        );
733
734        // Bind the TLS SNI to this session so the routing layer can reject any
735        // H2 stream whose `:authority` crosses the TLS trust boundary (see
736        // `route_from_request`).
737        context.tls_server_name = sni_owned;
738        context.tls_cert_names = tls_cert_names;
739        // Stamp the connection-scoped TLS metadata so every per-stream
740        // HttpContext created by `Context::create_stream` inherits it for
741        // the access log without re-querying rustls.
742        context.tls_version = tls_version_label;
743        context.tls_cipher = tls_cipher_label;
744        context.tls_alpn = alpn_label;
745        let mut frontend = match alpn {
746            AlpnProtocol::Http11 => {
747                incr!(names::http::ALPN_HTTP11);
748                context.create_stream(handshake.request_id, 1 << 16)?;
749                mux::Connection::new_h1_server(
750                    session_ulid,
751                    front_stream,
752                    handshake.container_frontend_timeout.duration(),
753                )
754            }
755            AlpnProtocol::H2 => {
756                incr!(names::http::ALPN_H2);
757                let flood_config = self.listener.borrow().get_h2_flood_config();
758                let connection_config = self.listener.borrow().get_h2_connection_config();
759                let stream_idle_timeout = self.listener.borrow().get_h2_stream_idle_timeout();
760                let graceful_shutdown_deadline =
761                    self.listener.borrow().get_h2_graceful_shutdown_deadline();
762                mux::Connection::new_h2_server(
763                    session_ulid,
764                    front_stream,
765                    &mut *context.buffers,
766                    handshake.container_frontend_timeout.duration(),
767                    flood_config,
768                    connection_config,
769                    stream_idle_timeout,
770                    graceful_shutdown_deadline,
771                )?
772            }
773        };
774        // Arm the upgraded connection. With TLS 1.3 + NewSessionTicket, the
775        // upgrade may happen from writable() where READABLE is no longer in the
776        // event (consumed by the prior readable() call). The HTTP/2 preface may
777        // already be in rustls's plaintext buffer (not on the TCP socket), so no
778        // new READABLE event from epoll will arrive: `upgraded_frontend_events`
779        // arms READABLE for it, and for an edge the handshake has not read yet,
780        // but not for a socket the handshake proved empty (#1609). Without
781        // WRITABLE in the event, the H2 state machine cannot transition from
782        // reading the preface to writing SETTINGS, causing a deadlock with
783        // clients (like hyper) that wait for the server's SETTINGS before
784        // proceeding.
785        frontend.readiness_mut().event.insert(frontend_events);
786
787        // Post-handoff: the mux frontend MUST be armed for WRITABLE, and for
788        // READABLE whenever a read is due without a new edge, or the H2
789        // preface→SETTINGS exchange deadlocks (see the comment above). This is
790        // the structural guarantee the insert just made — assert it survived.
791        debug_assert!(
792            frontend.readiness_mut().event.is_writable()
793                && (!frontend_events.is_readable() || frontend.readiness_mut().event.is_readable()),
794            "post-handshake mux frontend must be armed for WRITABLE, and READABLE when due"
795        );
796        // The two crate halves of the H2/H1 session reference streams by a shared
797        // ulid; the handshake-derived ulid must thread through both the connection
798        // and its context unchanged, otherwise per-stream lookups cross sessions.
799        debug_assert_eq!(
800            context.session_ulid, session_ulid,
801            "mux context and connection must share the handshake-derived session ulid"
802        );
803
804        gauge_add!(names::protocol::HTTPS, 1);
805        Some(HttpsStateMachine::Mux(Mux {
806            configured_frontend_timeout: self.configured_frontend_timeout,
807            frontend_token: self.frontend_token,
808            frontend,
809            context,
810            router,
811            session_ulid,
812            // Carry the already-armed handshake entry into the adapter rather
813            // than dropping it and arming a fresh one: the session's request
814            // timeout started when the socket was accepted.
815            timeouts: mux::MuxTimeouts::new(handshake.container_frontend_timeout),
816            backend_registry: mux::BackendRegistry::default(),
817            // Read once, at the upgrade: the proxy is not borrowed while a
818            // session runs, and `HttpsProxy::backends` is never reassigned,
819            // so this is the map the session would have reached on every dial.
820            backends: Rc::clone(&self.proxy.borrow().backends),
821        }))
822    }
823
824    fn upgrade_mux(&self, mut mux: MuxTls) -> Option<HttpsStateMachine> {
825        debug!("{} mux switching to wss", log_context!(self));
826        let Some(stream) = mux.context.streams.pop() else {
827            error!(
828                "{} upgrade_mux: no stream attached to the TLS mux session, closing",
829                log_context!(self)
830            );
831            return None;
832        };
833        // http.active_requests was already decremented by generate_access_log()
834        // in h1.rs before MuxResult::Upgrade was returned to us.
835
836        // The cores no longer own a wheel handle; the `Mux` adapter does. Move
837        // the frontend's out of the adapter before dismantling the
838        // connection, so the WebSocket `Pipe` inherits the same live entry it
839        // used to inherit from `ConnectionH1`.
840        let mut container_frontend_timeout = mux.timeouts.frontend;
841        let (frontend_readiness, frontend_socket) = match mux.frontend {
842            mux::Connection::H1(mux::ConnectionH1 {
843                readiness, socket, ..
844            }) => (readiness, socket),
845            mux::Connection::H2(_) => {
846                error!(
847                    "{} only h1<->h1 connections can upgrade to websocket",
848                    log_context!(self)
849                );
850                return None;
851            }
852        };
853
854        let mux::StreamState::Linked(back_token) = stream.state else {
855            error!(
856                "{} upgrading stream should be linked to a backend",
857                log_context!(self)
858            );
859            return None;
860        };
861        let Some(backend) = mux.router.backends.remove(&back_token) else {
862            error!(
863                "{} upgrade_mux: backend for token {:?} is missing (already disconnected?), closing",
864                log_context!(self),
865                back_token
866            );
867            return None;
868        };
869        let mut container_backend_timeout = mux
870            .timeouts
871            .backends
872            .remove(&back_token)
873            .unwrap_or_else(|| TimeoutContainer::new_empty(mux.router.configured_backend_timeout));
874        let (cluster_id, backend_id, backend_readiness, backend_socket) = match backend {
875            mux::Connection::H1(mux::ConnectionH1 {
876                position: mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected),
877                readiness,
878                socket,
879                ..
880            }) => (cluster_id, backend, readiness, socket),
881            mux::Connection::H1(_) => {
882                error!(
883                    "{} the backend disconnected just after upgrade, abort",
884                    log_context!(self)
885                );
886                return None;
887            }
888            mux::Connection::H2(_) => {
889                error!(
890                    "{} only h1<->h1 connections can upgrade to websocket",
891                    log_context!(self)
892                );
893                return None;
894            }
895        };
896
897        // The mux core carries an opaque `BackendId` (#1340, Q12); the
898        // WebSocket `Pipe` that takes this backend over still owns the
899        // registry handle, so resolve it here, on the embedder's side of that
900        // perimeter, through the one accessor there is.
901        let Some(backend) = mux.backend_registry.handle(&backend_id).cloned() else {
902            error!(
903                "{} upgrade_mux: backend {:?} names a slot this session never minted, closing",
904                log_context!(self),
905                backend_id.backend_id
906            );
907            return None;
908        };
909        let metric_incarnation = backend_id.metric_incarnation();
910        let backend_id = backend_id.backend_id.to_string();
911
912        let ws_context = stream.context.websocket_context();
913
914        container_frontend_timeout.reset();
915        container_backend_timeout.reset();
916
917        // Unwrap the `SessionTcpStream` that the mux put around every backend
918        // TCP socket — `Pipe::backend_socket` is typed `Option<TcpStream>`.
919        let backend_socket = backend_socket.stream;
920        let mut pipe = Pipe::new(
921            stream.back.storage.buffer,
922            Some(backend_id),
923            Some(backend_socket),
924            Some(backend),
925            Some(container_backend_timeout),
926            Some(container_frontend_timeout),
927            Some(cluster_id),
928            stream.front.storage.buffer,
929            self.frontend_token,
930            frontend_socket,
931            self.listener.clone(),
932            Protocol::HTTPS,
933            stream.context.session_id,
934            stream.context.id,
935            stream.context.session_address,
936            ws_context,
937        );
938        pipe.set_cluster_metrics_incarnation(metric_incarnation);
939
940        pipe.restore_readiness_events(frontend_readiness.event, backend_readiness.event);
941        pipe.set_back_token(back_token);
942        // The WSS pipe is a frontend↔backend bridge: it only exists because the
943        // upgrading stream was `Linked(back_token)` to a *connected* backend (the
944        // guard arms above already rejected `!Connected` and missing backends).
945        // So the back token must be set, and set to exactly the token we routed.
946        debug_assert!(
947            pipe.back_token().contains(&back_token),
948            "WSS pipe back token must be the connected backend token carried from the mux"
949        );
950        // Carry the connection-scoped TLS metadata captured at handshake time
951        // into the post-upgrade WSS pipe so its access log records the same
952        // version/cipher/sni/alpn the H1 request log already emitted. `clone`
953        // on the SNI is the only heap touch — the other three are
954        // `&'static str` borrows into the rustls label tables.
955        pipe.set_tls_metadata(
956            stream.context.tls_version,
957            stream.context.tls_cipher,
958            stream.context.tls_server_name.clone(),
959            stream.context.tls_alpn,
960        );
961
962        // Gauge accounting for the Mux→WebSocket transition is a balanced
963        // hand-off: exactly one HTTPS gauge leaves and one WSS gauge arrives, so
964        // the live-session total is conserved. The readiness events captured from
965        // the mux frontend/backend must survive the transfer into the pipe — a
966        // lost event would silently park the bridge with no epoll wake-up.
967        debug_assert_eq!(
968            pipe.frontend_readiness.event, frontend_readiness.event,
969            "WSS pipe must inherit the mux frontend readiness event verbatim"
970        );
971        debug_assert_eq!(
972            pipe.backend_readiness.event, backend_readiness.event,
973            "WSS pipe must inherit the mux backend readiness event verbatim"
974        );
975
976        // http.active_requests was already decremented by generate_access_log()
977        // in h1.rs when the 101 response was written (before MuxResult::Upgrade).
978        gauge_add!(names::protocol::HTTPS, -1);
979        gauge_add!(names::protocol::WSS, 1);
980        gauge_add!(names::websocket::ACTIVE_REQUESTS, 1);
981        Some(HttpsStateMachine::WebSocket(pipe))
982    }
983
984    fn upgrade_websocket(
985        &self,
986        wss: Pipe<FrontRustls, HttpsListener>,
987    ) -> Option<HttpsStateMachine> {
988        // what do we do here?
989        error!(
990            "{} upgrade called on WSS, this should not happen",
991            log_context!(self)
992        );
993        Some(HttpsStateMachine::WebSocket(wss))
994    }
995
996    /// Full cross-field invariant sweep for the session state machine, run as a
997    /// run-to-completion postcondition at the end of `ready()` (the public
998    /// mutating entry point). Encodes only relationships that must hold for any
999    /// live HTTPS session regardless of network input; a violation here is a
1000    /// Sōzu logic bug, never a property of hostile traffic.
1001    #[cfg(debug_assertions)]
1002    fn check_invariants(&self) {
1003        // Timeouts are immutable for the session's lifetime and were validated as
1004        // non-zero at construction; they must never drift to zero (which would
1005        // arm an immediate-firing deadline on the next state hand-off).
1006        debug_assert!(
1007            !self.configured_frontend_timeout.is_zero()
1008                && !self.configured_backend_timeout.is_zero()
1009                && !self.configured_connect_timeout.is_zero(),
1010            "HTTPS session timeouts must stay non-zero for the session lifetime"
1011        );
1012        // The marker is total over the four live stages plus FailedUpgrade; a
1013        // FailedUpgrade session is awaiting close and must report `failed()`,
1014        // while any of the four live stages must not. This is the structural
1015        // bridge the gauge-restore logic in `close()` relies on.
1016        if self.state.failed() {
1017            debug_assert!(
1018                matches!(
1019                    self.state.marker(),
1020                    StateMarker::Expect
1021                        | StateMarker::Handshake
1022                        | StateMarker::Mux
1023                        | StateMarker::WebSocket
1024                ),
1025                "FailedUpgrade must retain a valid origin-stage marker"
1026            );
1027        } else {
1028            debug_assert!(
1029                !self.state.failed(),
1030                "a non-failed session must not also report FailedUpgrade"
1031            );
1032        }
1033        // Before the PROXY header resolves, the Expect stage has no peer address;
1034        // once any later stage is reached the address is either the real peer
1035        // (direct TLS) or the PROXY-advertised source. We only assert the Expect
1036        // direction, since direct-TLS `peer_addr()` may legitimately fail and
1037        // leave `None` at handshake time.
1038        debug_assert!(
1039            !matches!(self.state.marker(), StateMarker::Expect)
1040                || self.peer_address.is_none()
1041                || self.state.failed(),
1042            "a live Expect-stage session has no resolved peer address yet"
1043        );
1044    }
1045}
1046
1047impl ProxySession for HttpsSession {
1048    fn close(&mut self) {
1049        if self.has_been_closed {
1050            return;
1051        }
1052        // Reaching past the idempotency guard means this is the *first* close.
1053        // Every exit below sets `has_been_closed = true`, so a clear flag here is
1054        // a real precondition (the gauge restore that follows must run exactly
1055        // once or the per-protocol gauge underflows / over-counts).
1056        debug_assert!(
1057            !self.has_been_closed,
1058            "close() body must run only on a not-yet-closed session"
1059        );
1060
1061        trace!("{} closing HTTPS session", log_context!(self));
1062        self.metrics.service_stop();
1063
1064        // Restore gauges
1065        match self.state.marker() {
1066            StateMarker::Expect => gauge_add!(names::protocol::PROXY_EXPECT, -1),
1067            StateMarker::Handshake => gauge_add!(names::protocol::TLS_HANDSHAKE, -1),
1068            StateMarker::Mux => gauge_add!(names::protocol::HTTPS, -1),
1069            StateMarker::WebSocket => {
1070                gauge_add!(names::protocol::WSS, -1);
1071                gauge_add!(names::websocket::ACTIVE_REQUESTS, -1);
1072            }
1073        }
1074
1075        if self.state.failed() {
1076            match self.state.marker() {
1077                StateMarker::Expect => incr!(names::https::UPGRADE_EXPECT_FAILED),
1078                StateMarker::Handshake => incr!(names::https::UPGRADE_HANDSHAKE_FAILED),
1079                StateMarker::Mux => incr!(names::https::UPGRADE_MUX_FAILED),
1080                StateMarker::WebSocket => incr!(names::https::UPGRADE_WSS_FAILED),
1081            }
1082            // FailedUpgrade means the socket was consumed by a failed upgrade
1083            // attempt, so we can only close the state (no-op) and remove the
1084            // session — cancel_timeouts / front_socket are unreachable.
1085            self.state.close(self.proxy.clone(), &mut self.metrics);
1086            self.proxy.borrow().remove_session(self.frontend_token);
1087            self.has_been_closed = true;
1088            return;
1089        }
1090
1091        self.state.cancel_timeouts();
1092        // defer backend closing to the state
1093        // in case of https it should also send a close notify on the client before the socket is closed below
1094        self.state.close(self.proxy.clone(), &mut self.metrics);
1095
1096        // Shut down the write side only. shutdown(Both) includes SHUT_RD which
1097        // discards unread data in the receive buffer (e.g. client's GOAWAY, ACKs).
1098        // On Linux, close() after SHUT_RD with discarded receive data sends TCP RST
1099        // instead of FIN, destroying any data still in the send buffer — including
1100        // TLS records that the drain loop just flushed. Using SHUT_WR only sends
1101        // FIN after all send buffer data is delivered, preserving the response.
1102        // Skipped once the client has closed its side: the close that follows
1103        // sends the same FIN or RST on its own (`mux::shutdown_write`). Over
1104        // TLS that includes a client whose EOF or `close_notify` a read has
1105        // already met (`FrontRustls::peer_disconnected`), which no epoll
1106        // event reports when the close follows in the same pass.
1107        let peer_closed = match &self.state {
1108            HttpsStateMachine::Mux(mux) => {
1109                mux.frontend_peer_closed()
1110                    || match &mux.frontend {
1111                        mux::Connection::H1(connection) => connection.socket.peer_disconnected,
1112                        mux::Connection::H2(connection) => connection.socket.peer_disconnected,
1113                    }
1114            }
1115            _ => false,
1116        };
1117        let front_socket = self.state.front_socket();
1118        if let Err(e) = mux::shutdown_write(front_socket, peer_closed) {
1119            // error 107 NotConnected can happen when was never fully connected, or was already disconnected due to error
1120            if e.kind() != ErrorKind::NotConnected {
1121                error!(
1122                    "{} error shutting down front socket({:?}): {:?}",
1123                    log_context!(self),
1124                    front_socket,
1125                    e
1126                );
1127            }
1128        }
1129
1130        // No `EPOLL_CTL_DEL` for the front socket: its only descriptor closes
1131        // when this session drops, which `shut_down_sessions_by_frontend_tokens`
1132        // (`lib/src/server.rs`) does before the event loop's next `epoll_wait`,
1133        // and Linux removes a file from every epoll set on its last close.
1134        // That holds because nothing duplicates a session socket: no `dup` or
1135        // `try_clone`, no fork from a worker, and SCM_RIGHTS only carries
1136        // listeners. The explicit deregister was one syscall per connection
1137        // that bought nothing; see §11 of `doc/lifetime_of_a_session.md`.
1138        let proxy = self.proxy.borrow();
1139        proxy.remove_session(self.frontend_token);
1140
1141        self.has_been_closed = true;
1142        // Postcondition: a session that completed `close()` is sealed — any later
1143        // `close()` short-circuits on the guard above, keeping the gauge restore
1144        // single-shot.
1145        debug_assert!(
1146            self.has_been_closed,
1147            "close() must leave the session marked closed"
1148        );
1149    }
1150
1151    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
1152        let session_result = self.state.timeout(token, &mut self.metrics);
1153        if session_result == StateResult::CloseSession {
1154            debug!(
1155                "{} HTTPS timeout requested close: token={:?}, marker={:?}",
1156                log_context!(self),
1157                token,
1158                self.state.marker()
1159            );
1160        }
1161        session_result == StateResult::CloseSession
1162    }
1163
1164    fn needs_ready_pass(&self) -> bool {
1165        matches!(&self.state, HttpsStateMachine::Mux(mux) if mux.has_pending_backend_failure())
1166    }
1167
1168    fn protocol(&self) -> Protocol {
1169        Protocol::HTTPS
1170    }
1171
1172    fn update_readiness(&mut self, token: Token, events: Ready) {
1173        trace!(
1174            "{} token {:?} got event {}",
1175            log_context!(self),
1176            token,
1177            super::ready_to_string(events)
1178        );
1179        self.last_event = Instant::now();
1180        self.metrics.wait_start();
1181        if token == self.frontend_token {
1182            // Whatever a `recv` proved about the frontend's receive queue,
1183            // bytes may have arrived since: `RecvMemory` (`lib/src/socket.rs`).
1184            match &mut self.state {
1185                HttpsStateMachine::Mux(mux) => mux
1186                    .frontend
1187                    .socket_handler_mut()
1188                    .readiness_delivered(events),
1189                HttpsStateMachine::WebSocket(pipe) => {
1190                    pipe.front_socket_handler_mut().readiness_delivered(events)
1191                }
1192                // No `FrontRustls` yet, or none any more.
1193                HttpsStateMachine::Expect(..)
1194                | HttpsStateMachine::Handshake(_)
1195                | HttpsStateMachine::FailedUpgrade(_) => {}
1196            }
1197        }
1198        self.state.update_readiness(token, events);
1199    }
1200
1201    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
1202        self.metrics.service_start();
1203
1204        let session_result =
1205            self.state
1206                .ready(session.clone(), self.proxy.clone(), &mut self.metrics);
1207
1208        let to_be_closed = match session_result {
1209            SessionResult::Close => true,
1210            SessionResult::Continue => false,
1211            SessionResult::Upgrade => match self.upgrade() {
1212                false => self.ready(session),
1213                true => true,
1214            },
1215        };
1216        if to_be_closed {
1217            debug!(
1218                "{} HTTPS ready requested close: marker={:?}",
1219                log_context!(self),
1220                self.state.marker()
1221            );
1222        }
1223
1224        // Run-to-completion postcondition: whatever state `ready()` (and any
1225        // nested upgrade) left the session in must satisfy the full cross-field
1226        // invariant set before we yield back to the event loop.
1227        #[cfg(debug_assertions)]
1228        self.check_invariants();
1229
1230        self.metrics.service_stop();
1231        to_be_closed
1232    }
1233
1234    fn shutting_down(&mut self) -> SessionIsToBeClosed {
1235        self.state.shutting_down()
1236    }
1237
1238    fn targets(&self, target: &crate::SessionTarget) -> bool {
1239        self.state.targets(target)
1240    }
1241
1242    fn last_event(&self) -> Instant {
1243        self.last_event
1244    }
1245
1246    fn print_session(&self) {
1247        self.state.print_state("HTTPS");
1248        error!("{} Metrics: {:?}", log_context!(self), self.metrics);
1249    }
1250
1251    fn frontend_token(&self) -> Token {
1252        self.frontend_token
1253    }
1254}
1255
1256pub type HostName = String;
1257pub type PathBegin = String;
1258
1259pub struct HttpsListener {
1260    active: bool,
1261    address: StdSocketAddr,
1262    answers: Rc<RefCell<HttpAnswers>>,
1263    config: HttpsListenerConfig,
1264    fronts: Router,
1265    listener: Option<MioTcpListener>,
1266    /// A socket this listener holds but has NOT registered.
1267    ///
1268    /// `activate()` parks an inherited socket here when `Registry::register`
1269    /// fails, instead of letting the `?` drop and close a descriptor
1270    /// `Listeners::get_*` has already removed from the SCM table (sozu#1342).
1271    /// The next activation reuses it rather than binding a second one.
1272    ///
1273    /// Deliberately NOT `listener`: every other consumer reads that field's `Some` as
1274    /// "registered and live" — `soft_stop` and `hard_stop` deregister it and
1275    /// report the failure, `give_back_listener` hands it back as an activated
1276    /// socket, `accept` accepts on it. A parked socket is none of those things,
1277    /// and putting it there made a failed registration answer `ENOENT` from
1278    /// `deregister` and fail the whole soft stop.
1279    parked_listener: Option<MioTcpListener>,
1280    /// Set by `activate()`, cleared by the first `accept()` that answers
1281    /// `WouldBlock`: while set, `accept()` sets `TCP_NODELAY` on each socket
1282    /// it returns, because a connection queued before `activate()` set the
1283    /// flag on the listener did not inherit it. See the field of the same
1284    /// name on `HttpListener` (`lib/src/http.rs`).
1285    nodelay_backlog: bool,
1286    resolver: Arc<MutexCertificateResolver>,
1287    rustls_details: Arc<RustlsServerConfig>,
1288    tags: BTreeMap<String, CachedTags>,
1289    token: Token,
1290}
1291
1292impl ListenerHandler for HttpsListener {
1293    fn get_addr(&self) -> &StdSocketAddr {
1294        &self.address
1295    }
1296
1297    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
1298        self.tags.get(key)
1299    }
1300
1301    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
1302        match tags {
1303            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
1304            None => self.tags.remove(&key),
1305        };
1306    }
1307
1308    fn protocol(&self) -> Protocol {
1309        Protocol::HTTPS
1310    }
1311
1312    fn public_address(&self) -> StdSocketAddr {
1313        self.config
1314            .public_address
1315            .map(|addr| addr.into())
1316            .unwrap_or(self.address)
1317    }
1318}
1319
1320impl L7ListenerHandler for HttpsListener {
1321    fn get_sticky_name(&self) -> &str {
1322        &self.config.sticky_name
1323    }
1324
1325    fn get_sozu_id_header(&self) -> &str {
1326        self.config
1327            .sozu_id_header
1328            .as_deref()
1329            .filter(|s| !s.is_empty())
1330            .unwrap_or("Sozu-Id")
1331    }
1332
1333    fn get_connect_timeout(&self) -> u32 {
1334        self.config.connect_timeout
1335    }
1336
1337    fn frontend_from_request(
1338        &self,
1339        host: &str,
1340        uri: &str,
1341        method: &Method,
1342        headers: &dyn RequestHeaders,
1343    ) -> Result<RouteResult, FrontendFromRequestError> {
1344        let start = Instant::now();
1345        let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
1346            Ok(tuple) => tuple,
1347            Err(parse_error) => {
1348                // parse_error contains a slice of given_host, which should NOT escape this scope
1349                return Err(FrontendFromRequestError::HostParse {
1350                    host: host.to_owned(),
1351                    error: parse_error.to_string(),
1352                });
1353            }
1354        };
1355
1356        if remaining_input != &b""[..] {
1357            return Err(FrontendFromRequestError::InvalidCharsAfterHost(
1358                host.to_owned(),
1359            ));
1360        }
1361
1362        // it is alright to call from_utf8_unchecked,
1363        // we already verified that there are only ascii
1364        // chars in there
1365        // SAFETY: `hostname` was just produced by `hostname_and_port`
1366        // (`lib/src/protocol/kawa_h1/parser.rs`), which only accepts
1367        // bytes matching `is_hostname_char` (alphanumeric, `-`, `.`, plus
1368        // `_` under the tolerant-http1-parser feature). All accepted
1369        // bytes are ASCII (≤ 0x7F), so the slice is valid single-byte UTF-8.
1370        let host = unsafe { from_utf8_unchecked(hostname) };
1371
1372        let route = self
1373            .fronts
1374            .lookup_with_headers(host, uri, method, headers)
1375            .map_err(|e| {
1376                incr!(names::http::FAILED_BACKEND_MATCHING);
1377                FrontendFromRequestError::NoClusterFound(e)
1378            })?;
1379
1380        let now = Instant::now();
1381
1382        if let Some(cluster) = route.cluster_id.as_deref() {
1383            time!(
1384                names::event_loop::FRONTEND_MATCHING_TIME,
1385                cluster,
1386                (now - start).as_millis()
1387            );
1388        }
1389
1390        Ok(route)
1391    }
1392
1393    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
1394        &self.answers
1395    }
1396
1397    fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
1398        // `H2FloodConfig`'s fields are private and `from_optional` applies the
1399        // `.max(1)` clamp `new` carries (sozu-proxy/sozu#1418). This used to be
1400        // a raw struct literal, which skipped that clamp on the only path that
1401        // carries operator-set thresholds: config load rejects an out-of-range
1402        // knob (`ConfigError::H2ThresholdBelowMinimum`), and the clamp is the
1403        // structural backstop for a raw protobuf `AddHttpsListener` or a
1404        // state file saved before that gate existed.
1405        crate::protocol::mux::H2FloodConfig::from_optional(
1406            self.config.h2_max_rst_stream_per_window,
1407            self.config.h2_max_ping_per_window,
1408            self.config.h2_max_settings_per_window,
1409            self.config.h2_max_empty_data_per_window,
1410            self.config.h2_max_window_update_stream0_per_window,
1411            self.config.h2_max_continuation_frames,
1412            self.config.h2_max_glitch_count,
1413            self.config.h2_max_rst_stream_lifetime,
1414            self.config.h2_max_rst_stream_abusive_lifetime,
1415            self.config.h2_max_rst_stream_emitted_lifetime,
1416            self.config.h2_max_header_list_size,
1417            self.config.h2_max_header_table_size,
1418            self.config.h2_max_header_fields,
1419            self.config.h2_stream_refusal_percent,
1420        )
1421    }
1422
1423    fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1424        crate::protocol::mux::H2ConnectionConfig::from_optional(
1425            self.config.h2_initial_connection_window,
1426            self.config.h2_max_concurrent_streams,
1427            self.config.h2_stream_shrink_ratio,
1428        )
1429    }
1430
1431    fn get_strict_sni_binding(&self) -> bool {
1432        // SNI↔:authority binding is enforced by default (closes
1433        // CWE-346 / CWE-444); this listener knob preserves that
1434        // behavior by default and lets operators opt out when cross-SNI
1435        // routing is intentional.
1436        //
1437        // Note: `strict_sni_binding = false` theoretically allows an
1438        // attacker to present many distinct SNIs on the same TCP
1439        // connection. rustls 0.23 **bans TLS renegotiation outright** (see
1440        // `rustls::server::ClientHello` which is consumed during the initial
1441        // handshake only), so a single TCP connection gets exactly one SNI
1442        // for its lifetime — the cross-SNI-flood vector is not reachable in
1443        // practice. Kept documented here so a future rustls upgrade that
1444        // reintroduces renegotiation (vanishingly unlikely) surfaces the
1445        // assumption during review.
1446        self.config.strict_sni_binding.unwrap_or(true)
1447    }
1448
1449    fn get_elide_x_real_ip(&self) -> bool {
1450        self.config.elide_x_real_ip.unwrap_or(false)
1451    }
1452
1453    fn get_send_x_real_ip(&self) -> bool {
1454        self.config.send_x_real_ip.unwrap_or(false)
1455    }
1456
1457    fn get_forwarded_headers(&self) -> ForwardedHeaders {
1458        // An unknown wire value reads as the default, like prost's own
1459        // getter; `update_config` and the main-process state refuse one.
1460        self.config.forwarded_headers()
1461    }
1462
1463    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1464        // Inherit `back_timeout` when the knob is unset so listeners tuned for
1465        // long-running backends do not cancel streams at the 30 s security
1466        // floor. The `max(30, …)` keeps the baseline slow-multiplex mitigation
1467        // when `back_timeout` is shorter than 30 s. Explicit values (including
1468        // ones below 30 s) win — operators under a slow-multiplex attack can
1469        // lower the per-stream deadline to cap buffer pinning.
1470        let seconds = self
1471            .config
1472            .h2_stream_idle_timeout_seconds
1473            .map(|s| u64::from(s.max(1)))
1474            .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1475        std::time::Duration::from_secs(seconds)
1476    }
1477
1478    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1479        match self.config.h2_graceful_shutdown_deadline_seconds {
1480            None => Some(std::time::Duration::from_secs(5)),
1481            Some(0) => None,
1482            Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1483        }
1484    }
1485}
1486
1487impl HttpsListener {
1488    /// This listener's identity: its address, plus the network interface its
1489    /// socket is bound to when it has one.
1490    pub fn key(&self) -> ListenerKey {
1491        ListenerKey::new(self.address, self.config.interface.as_deref())
1492    }
1493
1494    /// Whether this listener is the one `key` names.
1495    pub fn is(&self, key: &ListenerKey) -> bool {
1496        key.matches(&self.address, self.config.interface.as_deref())
1497    }
1498
1499    /// Whether this listener rejects clients that do not negotiate `h2`
1500    /// via TLS ALPN (including those that omit ALPN). Reads the
1501    /// `disable_http11` knob; defaults to `false` to preserve the
1502    /// historical behavior where a missing ALPN silently downgrades
1503    /// to HTTP/1.1.
1504    pub fn is_http11_disabled(&self) -> bool {
1505        self.config.disable_http11.unwrap_or(false)
1506    }
1507
1508    /// Borrow the listener's certificate resolver. Used by the TLS handshake
1509    /// path to snapshot the SAN set of the certificate Sōzu serves for a
1510    /// given SNI, so the H2 router can accept connection coalescing
1511    /// (RFC 7540 §9.1.1 / RFC 9113 §9.1.1) on every authority covered by
1512    /// that cert (RFC 6125 §6.4.3 wildcard handling).
1513    pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1514        &self.resolver
1515    }
1516
1517    pub fn try_new(
1518        config: HttpsListenerConfig,
1519        token: Token,
1520    ) -> Result<HttpsListener, ListenerError> {
1521        // Defense in depth, as `update_config`: the main-process state
1522        // refuses an unknown mode, a raw protobuf client may not.
1523        if let Some(v) = config.forwarded_headers {
1524            validate_forwarded_headers(v)?;
1525        }
1526        let resolver = Arc::new(MutexCertificateResolver::default());
1527
1528        let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1529
1530        let answers = Self::build_answers(&config)?;
1531
1532        Ok(HttpsListener {
1533            listener: None,
1534            parked_listener: None,
1535            nodelay_backlog: false,
1536            address: config.address.into(),
1537            resolver,
1538            rustls_details: server_config,
1539            active: false,
1540            fronts: Router::new(),
1541            answers: Rc::new(RefCell::new(answers)),
1542            config,
1543            token,
1544            tags: BTreeMap::new(),
1545        })
1546    }
1547
1548    /// Build the listener's HTTP answer templates from its config, reconciling
1549    /// the legacy `http_answers` per-status fields with the new `answers`
1550    /// template map (the new map wins on collision; legacy fields fill any
1551    /// status not yet migrated). Shared by [`Self::try_new`] and
1552    /// [`Self::validate_config`] so the master-side pre-commit check cannot
1553    /// drift from worker construction.
1554    fn build_answers(config: &HttpsListenerConfig) -> Result<HttpAnswers, ListenerError> {
1555        let mut answers_map = config.answers.clone();
1556        if let Some(ref legacy) = config.http_answers {
1557            crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1558        }
1559        HttpAnswers::new(&answers_map)
1560            .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1561    }
1562
1563    /// Validate that a worker can build this HTTPS listener configuration — the
1564    /// rustls context and the answer templates — WITHOUT constructing the full
1565    /// listener or binding a socket. Runs the exact fallible steps of
1566    /// [`Self::try_new`] (via the shared [`Self::create_rustls_context`] /
1567    /// `Self::build_answers` helpers), so master-side validation is faithful
1568    /// to what the worker will do.
1569    ///
1570    /// The main process calls this before committing an `AddHttpsListener` to
1571    /// its `ConfigState` and fanning it out, so an invalid listener never
1572    /// reserves its address and blocks a corrected reload (sozu#1301).
1573    pub fn validate_config(config: &HttpsListenerConfig) -> Result<(), ListenerError> {
1574        // Buildability only; the H2 knob floors are enforced beside their
1575        // audience in `bin/src/command/requests.rs::validate_h2_knob_floors`
1576        // (sozu#1418), because a worker CAN build an out-of-range listener —
1577        // `H2FloodConfig::from_optional` clamps it.
1578        //
1579        // An empty resolver matches `try_new`: the default certificate is added
1580        // later via `AddCertificate`, so only the config-level TLS parameters
1581        // (versions, ciphers, groups, ALPN) and the answer templates are
1582        // exercised here.
1583        Self::create_rustls_context(config, Arc::new(MutexCertificateResolver::default()))?;
1584        Self::build_answers(config)?;
1585        Ok(())
1586    }
1587
1588    pub fn activate(
1589        &mut self,
1590        registry: &Registry,
1591        tcp_listener: Option<MioTcpListener>,
1592    ) -> Result<Token, ListenerError> {
1593        if self.active {
1594            return Ok(self.token);
1595        }
1596        let address: StdSocketAddr = self.config.address.into();
1597
1598        // Reuse a socket a previous attempt parked here after a failed
1599        // registration, rather than binding a second one. Taking it
1600        // unconditionally is what keeps the two from coexisting: if the caller
1601        // ALSO handed one over, the parked socket is disposed of deliberately
1602        // below instead of being silently overwritten.
1603        let parked = self.parked_listener.take();
1604        let mut listener = match (tcp_listener, parked) {
1605            (Some(inherited), Some(parked)) => {
1606                // Not reachable while `Listeners::get_*` removes the entry it
1607                // hands over and `scm_listeners` is written once: a parked
1608                // socket means this address's entry was already consumed, so
1609                // the caller cannot have another. Neither of those is enforced
1610                // here, so dispose of the loser explicitly and loudly rather
1611                // than letting an assignment close it — that silent close is
1612                // sozu#1342 itself.
1613                warn!(
1614                    "{} closing the socket parked on the HTTPS listener for {}: an \
1615                     inherited socket arrived for the same address and is the one being \
1616                     activated",
1617                    log_module_context!(),
1618                    address
1619                );
1620                drop(parked);
1621                inherited
1622            }
1623            (Some(inherited), None) => inherited,
1624            (None, Some(parked)) => parked,
1625            (None, None) => server_bind(address, self.config.interface.as_deref()).map_err(
1626                |server_bind_error| ListenerError::Activation {
1627                    address,
1628                    error: server_bind_error.to_string(),
1629                },
1630            )?,
1631        };
1632
1633        // Once per listener, on whichever socket won above — freshly bound,
1634        // inherited over SCM_RIGHTS, or parked. Every socket accepted from now
1635        // on inherits it; see `nodelay_backlog` for the ones already queued.
1636        if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
1637            error!(
1638                "{} error setting nodelay on listen socket({:?}): {:?}",
1639                log_module_context!(),
1640                listener,
1641                e
1642            );
1643        }
1644
1645        let registration = registry
1646            .register(&mut listener, self.token, Interest::READABLE)
1647            .map_err(ListenerError::SocketRegistration);
1648        if let Err(error) = registration {
1649            // Park the socket instead of letting the `?` drop it. `register`
1650            // is fallible (EEXIST for a descriptor still registered because
1651            // `notify_deactivate_listener` only logs a failed deregister,
1652            // EBADF, ENOMEM), and a socket still held in a local is dropped —
1653            // and closed — on the way out. For an SCM-inherited socket that is
1654            // the sozu#1342 close in a second place: `Listeners::get_*` has
1655            // already removed the entry, so nothing else owns the descriptor.
1656            // It goes to `parked_listener`, never to `listener`, because every other
1657            // consumer of that field reads `Some` as "registered and live".
1658            self.parked_listener = Some(listener);
1659            return Err(error);
1660        }
1661
1662        self.listener = Some(listener);
1663        self.nodelay_backlog = true;
1664        self.active = true;
1665        // Post: an activated listener owns a bound socket and is flagged active,
1666        // so a later `activate()` short-circuits on the `self.active` guard and
1667        // `give_back_listener*` find a socket to hand back. The two must move in
1668        // lockstep — an active listener with no socket would silently accept
1669        // nothing.
1670        debug_assert!(
1671            self.active && self.listener.is_some(),
1672            "an activated HTTPS listener must hold a bound socket and be flagged active"
1673        );
1674        Ok(self.token)
1675    }
1676
1677    pub fn create_rustls_context(
1678        config: &HttpsListenerConfig,
1679        resolver: Arc<MutexCertificateResolver>,
1680    ) -> Result<RustlsServerConfig, ListenerError> {
1681        let cipher_names = if config.cipher_list.is_empty() {
1682            DEFAULT_CIPHER_LIST.to_vec()
1683        } else {
1684            config
1685                .cipher_list
1686                .iter()
1687                .map(|s| s.as_str())
1688                .collect::<Vec<_>>()
1689        };
1690
1691        let ciphers = cipher_names
1692            .into_iter()
1693            .filter_map(|cipher| {
1694                cipher_suite_by_name(cipher).or_else(|| {
1695                    error!(
1696                        "{} unknown or unsupported cipher: {:?}",
1697                        log_module_context!(),
1698                        cipher
1699                    );
1700                    None
1701                })
1702            })
1703            .collect::<Vec<_>>();
1704
1705        let versions = config
1706            .versions
1707            .iter()
1708            .filter_map(|version| match TlsVersion::try_from(*version) {
1709                Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1710                Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1711                Ok(other_version) => {
1712                    error!(
1713                        "{} unsupported TLS version {:?}",
1714                        log_module_context!(),
1715                        other_version
1716                    );
1717                    None
1718                }
1719                Err(_) => {
1720                    error!("{} unsupported TLS version", log_module_context!());
1721                    None
1722                }
1723            })
1724            .collect::<Vec<_>>();
1725
1726        let kx_groups = if config.groups_list.is_empty() {
1727            default_provider().kx_groups
1728        } else {
1729            config
1730                .groups_list
1731                .iter()
1732                .filter_map(|group| match kx_group_by_name(group) {
1733                    Some(kx) => Some(kx),
1734                    None => {
1735                        debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1736                        None
1737                    }
1738                })
1739                .collect::<Vec<_>>()
1740        };
1741
1742        let provider = Arc::new(CryptoProvider {
1743            cipher_suites: ciphers,
1744            kx_groups,
1745            ..default_provider()
1746        });
1747
1748        let builder = RustlsServerConfig::builder_with_provider(provider.clone())
1749            .with_protocol_versions(&versions[..])
1750            .map_err(|err| ListenerError::BuildRustls(err.to_string()))?;
1751
1752        // mTLS: `client_auth` decodes to `ClientAuthMode` (NONE at field 0).
1753        // NONE keeps the historical `.with_no_client_auth()`; OPTIONAL and
1754        // REQUIRED install a WebPki client-certificate verifier built from the
1755        // listener's trusted CA bundle (and optional CRLs). OPTIONAL differs
1756        // only by allowing connections that present no certificate at all. The
1757        // verifier is built from the same `provider` as the server config so it
1758        // works in single-provider builds (`crypto-openssl`-only) and in
1759        // multi-provider builds (`--all-features`) where no process-default
1760        // provider is installed.
1761        let builder = match Self::client_cert_verifier(config, &provider)? {
1762            Some(verifier) => builder.with_client_cert_verifier(verifier),
1763            None => builder.with_no_client_auth(),
1764        };
1765
1766        let mut server_config = builder.with_cert_resolver(resolver);
1767        server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1768
1769        server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1770            DEFAULT_ALPN_PROTOCOLS
1771                .iter()
1772                .map(|p| p.as_bytes().to_vec())
1773                .collect()
1774        } else {
1775            config
1776                .alpn_protocols
1777                .iter()
1778                .map(|p| p.as_bytes().to_vec())
1779                .collect()
1780        };
1781
1782        Ok(server_config)
1783    }
1784
1785    /// Refuse two CRLs of one CA whose scopes overlap: webpki checks a
1786    /// certificate against the first configured CRL that is authoritative for
1787    /// it and never reads the others, so the order alone would decide, and an
1788    /// old CRL listed before its replacement would hide every revocation the
1789    /// replacement adds. A CRL without an `IssuingDistributionPoint` covers
1790    /// everything its CA issued; partitions of one CA, each with its own
1791    /// distribution point, do not overlap and are accepted together.
1792    ///
1793    /// Issuers and distribution points are compared as the raw DER webpki
1794    /// compares. The check does not depend on time, so every worker runs it
1795    /// when it builds the listener; [`Self::check_crls_current`] is the one
1796    /// that does.
1797    fn check_crl_set(crls: &[CertificateRevocationListDer<'_>]) -> Result<(), ListenerError> {
1798        let mut scopes: Vec<(Vec<u8>, Option<Vec<u8>>)> = Vec::with_capacity(crls.len());
1799        for (index, crl) in crls.iter().enumerate() {
1800            let parsed = webpki::BorrowedCertRevocationList::from_der(crl.as_ref())
1801                .map_err(|e| ListenerError::ClientAuth(format!("invalid CRL: {e}")))?;
1802            let parsed = webpki::CertRevocationList::from(parsed);
1803            let issuer = parsed.issuer().to_vec();
1804            let distribution_point = parsed.issuing_distribution_point().map(<[u8]>::to_vec);
1805            let overlapping = scopes.iter().position(|(seen_issuer, seen_point)| {
1806                *seen_issuer == issuer
1807                    && (seen_point.is_none()
1808                        || distribution_point.is_none()
1809                        || *seen_point == distribution_point)
1810            });
1811            if let Some(first) = overlapping {
1812                return Err(ListenerError::ClientAuth(format!(
1813                    "CRLs #{} and #{} (counted across client_ca_crls) are issued by the \
1814                     same CA for overlapping certificates, and only the first would be \
1815                     consulted: configure one CRL per issuer, or one per distribution point",
1816                    first + 1,
1817                    index + 1
1818                )));
1819            }
1820            scopes.push((issuer, distribution_point));
1821        }
1822        Ok(())
1823    }
1824
1825    /// Refuse a CRL already past its `nextUpdate` at `now` (seconds since the
1826    /// Unix epoch): with revocation expiry enforced it would reject every
1827    /// client it covers, which is better reported when the operator states it
1828    /// than found at the first handshake.
1829    ///
1830    /// Main process only, on what an operator states (configuration load,
1831    /// reload, a client request), never on a replay or in a worker: a CRL
1832    /// that lapses while the listener runs must not stop a worker started
1833    /// later from building the listener, or the reload that brings a current
1834    /// CRL would find nothing to patch there. Such a worker builds it and
1835    /// rejects the clients at handshake until that reload.
1836    pub fn check_crls_current(crls: &[String], now: i64) -> Result<(), ListenerError> {
1837        let mut index = 0;
1838        for pem in crls {
1839            for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1840                let crl =
1841                    crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?;
1842                index += 1;
1843                if let Some(next_update) = sozu_command::certificate::crl_next_update(crl.as_ref())
1844                    && next_update <= now
1845                {
1846                    return Err(ListenerError::ClientAuth(format!(
1847                        "CRL #{index} (counted across client_ca_crls) expired {} seconds ago: \
1848                         its nextUpdate is past, refresh it",
1849                        now - next_update
1850                    )));
1851                }
1852            }
1853        }
1854        Ok(())
1855    }
1856
1857    /// Check a client authentication policy the way a listener would build
1858    /// it, without a listener: the main process runs this on an
1859    /// `UpdateHttpsListenerConfig.client_auth_policy` patch before it commits
1860    /// the patch to its state, so a policy no worker could build is refused
1861    /// up front rather than fanned out.
1862    pub fn validate_client_auth_policy(policy: &ClientAuthPolicy) -> Result<(), ListenerError> {
1863        let mut config = HttpsListenerConfig::default();
1864        config.apply_client_auth_policy(policy);
1865        Self::client_cert_verifier(&config, &Arc::new(default_provider())).map(|_| ())
1866    }
1867
1868    /// Build the rustls client-certificate verifier for a listener's mTLS
1869    /// configuration, or `None` when client auth is disabled.
1870    ///
1871    /// Returns `None` only for [`ClientAuthMode::ClientAuthNone`] (and for an
1872    /// absent `client_auth` field, which decodes to that variant), preserving
1873    /// the historical `.with_no_client_auth()` path. For OPTIONAL/REQUIRED it
1874    /// parses `client_ca_certificates` into a root store and, when present,
1875    /// `client_ca_crls` into revocation lists. OPTIONAL additionally allows
1876    /// unauthenticated clients (no certificate presented); REQUIRED aborts the
1877    /// handshake in that case.
1878    ///
1879    /// The verifier is built with the caller's `provider` so it never relies on
1880    /// a process-default crypto provider (which may be absent or ambiguous). An
1881    /// unknown `client_auth` value is rejected rather than treated as NONE, so a
1882    /// malformed or future enum value can never silently disable client auth.
1883    fn client_cert_verifier(
1884        config: &HttpsListenerConfig,
1885        provider: &Arc<CryptoProvider>,
1886    ) -> Result<Option<Arc<dyn rustls::server::danger::ClientCertVerifier>>, ListenerError> {
1887        // Reject unknown enum values instead of failing open to NONE.
1888        let raw = config
1889            .client_auth
1890            .unwrap_or(ClientAuthMode::ClientAuthNone as i32);
1891        let mode = ClientAuthMode::try_from(raw).map_err(|_| {
1892            ListenerError::ClientAuth(format!("unknown client_auth mode value {raw}"))
1893        })?;
1894        if mode == ClientAuthMode::ClientAuthNone {
1895            return Ok(None);
1896        }
1897
1898        // Parse every trusted-CA PEM entry into the root store. Each entry may
1899        // itself hold a concatenated PEM chain, so iterate per entry. An entry
1900        // that contributes no certificate (empty text, or PEM carrying another
1901        // section type) is rejected rather than skipped: it would start the
1902        // listener with only a subset of the configured trust anchors, so
1903        // clients issued by the omitted CA would fail with no visible cause.
1904        let mut roots = RootCertStore::empty();
1905        for pem in &config.client_ca_certificates {
1906            let before = roots.len();
1907            for cert in CertificateDer::pem_slice_iter(pem.as_bytes()) {
1908                let cert =
1909                    cert.map_err(|e| ListenerError::ClientAuth(format!("invalid CA PEM: {e}")))?;
1910                roots
1911                    .add(cert)
1912                    .map_err(|e| ListenerError::ClientAuth(format!("untrusted CA: {e}")))?;
1913            }
1914            if roots.len() == before {
1915                return Err(ListenerError::ClientAuth(
1916                    "a configured trusted-CA entry contained no certificate".to_owned(),
1917                ));
1918            }
1919        }
1920        if roots.is_empty() {
1921            return Err(ListenerError::ClientAuth(
1922                "client auth requested but no trusted CA certificate was provided".to_owned(),
1923            ));
1924        }
1925
1926        // Parse CRLs per configured entry. An entry that yields zero CRLs (empty
1927        // text, or PEM carrying another section type) is a misconfiguration: it
1928        // would silently disable revocation, so reject it rather than build a
1929        // verifier that skips `with_crls`.
1930        let mut crls = Vec::new();
1931        for pem in &config.client_ca_crls {
1932            let before = crls.len();
1933            for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1934                crls.push(
1935                    crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?,
1936                );
1937            }
1938            if crls.len() == before {
1939                return Err(ListenerError::ClientAuth(
1940                    "a configured CRL entry contained no certificate revocation list".to_owned(),
1941                ));
1942            }
1943        }
1944        Self::check_crl_set(&crls)?;
1945
1946        let mut builder =
1947            WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone());
1948        if !crls.is_empty() {
1949            // rustls defaults to `ExpirationPolicy::Ignore`, which keeps trusting
1950            // a CRL past its `nextUpdate`. Enforce expiration so a stale CRL is
1951            // rejected rather than silently accepting certificates whose
1952            // revocation status is no longer known.
1953            builder = builder.with_crls(crls).enforce_revocation_expiration();
1954        }
1955        if mode == ClientAuthMode::ClientAuthOptional {
1956            builder = builder.allow_unauthenticated();
1957        }
1958        let verifier = builder
1959            .build()
1960            .map_err(|e| ListenerError::ClientAuth(e.to_string()))?;
1961        Ok(Some(verifier))
1962    }
1963
1964    /// Apply a partial-update patch to this listener's live configuration.
1965    ///
1966    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
1967    /// If `alpn_protocols` is present the rustls `ServerConfig` is rebuilt —
1968    /// in-flight handshakes keep the old Arc; new ones see the new one.
1969    /// If `http_answers` is present only the listener-default templates are
1970    /// replaced; per-cluster overrides in `cluster_custom_answers` are kept.
1971    ///
1972    /// All-or-nothing (sozu-proxy/sozu#1703): the patch is validated, then
1973    /// applied to a copy of the configuration, and every fallible artifact
1974    /// (the rustls context, the answer registry) is built from that copy.
1975    /// Only once all of it succeeded are the copy and the artifacts
1976    /// committed and the inheriting frontends' HSTS refreshed, so a refused
1977    /// patch leaves the listener exactly as it was. The copy is one
1978    /// `HttpsListenerConfig` clone per patch, on the command path only.
1979    pub fn update_config(
1980        &mut self,
1981        patch: &UpdateHttpsListenerConfig,
1982    ) -> Result<(), ListenerError> {
1983        // Defense-in-depth validation: main-process ConfigState::dispatch
1984        // validates before scatter, but a raw protobuf client or state replay
1985        // may reach the worker without that check. `StateError` lifts into
1986        // `ListenerError` via `From` so `?` suffices. The main process runs
1987        // the same checks (`ConfigState::update_https_listener` and, for the
1988        // templates, `validate_listener_request` in
1989        // `bin/src/command/requests.rs`), so both refuse the same patches.
1990        validate_h2_flood_knobs_https(patch)?;
1991        if let Some(ref alpn) = patch.alpn_protocols {
1992            validate_alpn_protocols(&alpn.values)?;
1993        }
1994        if let Some(ref hdr) = patch.sozu_id_header {
1995            validate_sozu_id_header(hdr)?;
1996        }
1997        if let Some(v) = patch.forwarded_headers {
1998            validate_forwarded_headers(v)?;
1999        }
2000        // HSTS is a full-object replacement, and `enabled` is the explicit
2001        // disambiguator between "disable" and "enable" semantics: the
2002        // operator must signal one or the other on every update, so a block
2003        // without it is refused. The main process refuses it too, with
2004        // `StateError::InvalidValue` (`validate_hsts_patch`,
2005        // `command/src/state.rs`).
2006        if patch.hsts.is_some_and(|hsts| hsts.enabled.is_none()) {
2007            return Err(ListenerError::HstsEnabledRequired);
2008        }
2009        HttpAnswers::validate_patch_templates(patch.http_answers.as_ref(), &patch.answers)
2010            .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
2011
2012        let mut config = self.config.clone();
2013
2014        // --- simple field patches ---
2015        if let Some(v) = patch.public_address {
2016            config.public_address = Some(v);
2017        }
2018        if let Some(v) = patch.expect_proxy {
2019            config.expect_proxy = v;
2020        }
2021        if let Some(ref v) = patch.sticky_name {
2022            config.sticky_name = v.to_owned();
2023        }
2024        if let Some(v) = patch.front_timeout {
2025            config.front_timeout = v;
2026        }
2027        if let Some(v) = patch.back_timeout {
2028            config.back_timeout = v;
2029        }
2030        if let Some(v) = patch.connect_timeout {
2031            config.connect_timeout = v;
2032        }
2033        if let Some(v) = patch.request_timeout {
2034            config.request_timeout = v;
2035        }
2036        if let Some(v) = patch.strict_sni_binding {
2037            config.strict_sni_binding = Some(v);
2038        }
2039        if let Some(v) = patch.disable_http11 {
2040            config.disable_http11 = Some(v);
2041        }
2042        if let Some(ref v) = patch.sozu_id_header {
2043            config.sozu_id_header = Some(v.to_owned());
2044        }
2045        if let Some(v) = patch.elide_x_real_ip {
2046            config.elide_x_real_ip = Some(v);
2047        }
2048        if let Some(v) = patch.send_x_real_ip {
2049            config.send_x_real_ip = Some(v);
2050        }
2051        if let Some(v) = patch.forwarded_headers {
2052            config.forwarded_headers = Some(v);
2053        }
2054
2055        // --- H2 flood knobs ---
2056        if let Some(v) = patch.h2_max_rst_stream_per_window {
2057            config.h2_max_rst_stream_per_window = Some(v);
2058        }
2059        if let Some(v) = patch.h2_max_ping_per_window {
2060            config.h2_max_ping_per_window = Some(v);
2061        }
2062        if let Some(v) = patch.h2_max_settings_per_window {
2063            config.h2_max_settings_per_window = Some(v);
2064        }
2065        if let Some(v) = patch.h2_max_empty_data_per_window {
2066            config.h2_max_empty_data_per_window = Some(v);
2067        }
2068        if let Some(v) = patch.h2_max_continuation_frames {
2069            config.h2_max_continuation_frames = Some(v);
2070        }
2071        if let Some(v) = patch.h2_max_glitch_count {
2072            config.h2_max_glitch_count = Some(v);
2073        }
2074        if let Some(v) = patch.h2_initial_connection_window {
2075            config.h2_initial_connection_window = Some(v);
2076        }
2077        if let Some(v) = patch.h2_max_concurrent_streams {
2078            config.h2_max_concurrent_streams = Some(v);
2079        }
2080        if let Some(v) = patch.h2_stream_shrink_ratio {
2081            config.h2_stream_shrink_ratio = Some(v);
2082        }
2083        if let Some(v) = patch.h2_max_rst_stream_lifetime {
2084            config.h2_max_rst_stream_lifetime = Some(v);
2085        }
2086        if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
2087            config.h2_max_rst_stream_abusive_lifetime = Some(v);
2088        }
2089        if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
2090            config.h2_max_rst_stream_emitted_lifetime = Some(v);
2091        }
2092        if let Some(v) = patch.h2_max_header_list_size {
2093            config.h2_max_header_list_size = Some(v);
2094        }
2095        if let Some(v) = patch.h2_max_header_table_size {
2096            config.h2_max_header_table_size = Some(v);
2097        }
2098        if let Some(v) = patch.h2_max_header_fields {
2099            config.h2_max_header_fields = Some(v);
2100        }
2101        if let Some(v) = patch.h2_stream_refusal_percent {
2102            config.h2_stream_refusal_percent = Some(v);
2103        }
2104        if let Some(v) = patch.h2_stream_idle_timeout_seconds {
2105            config.h2_stream_idle_timeout_seconds = Some(v);
2106        }
2107        if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
2108            config.h2_graceful_shutdown_deadline_seconds = Some(v);
2109        }
2110        if let Some(v) = patch.h2_max_window_update_stream0_per_window {
2111            config.h2_max_window_update_stream0_per_window = Some(v);
2112        }
2113
2114        if let Some(new_hsts) = patch.hsts {
2115            config.hsts = Some(new_hsts);
2116        }
2117        if let Some(ref alpn_wrapper) = patch.alpn_protocols {
2118            // Empty values vec = reset to default (runtime treats empty as default)
2119            config.alpn_protocols = alpn_wrapper.values.clone();
2120        }
2121        if let Some(ref policy) = patch.client_auth_policy {
2122            config.apply_client_auth_policy(policy);
2123        }
2124
2125        // --- ALPN rebuild (may force a rustls ServerConfig rebuild) ---
2126        //
2127        // Built from the candidate config, which carries the new ALPN, and
2128        // published below only once every other fallible step succeeded, so
2129        // a rustls failure (crypto provider transient, resolver error, etc.)
2130        // leaves the listener observably unchanged. The main-process state
2131        // would still diverge from that worker-side refusal: the main process
2132        // cannot build this worker's rustls context before it commits.
2133        // A client authentication policy lives in the rustls context's client
2134        // certificate verifier, so a new policy needs the same rebuild; an
2135        // invalid one fails it here and the listener keeps its old policy.
2136        let new_rustls = if patch.alpn_protocols.is_some() || patch.client_auth_policy.is_some() {
2137            Some(Arc::new(Self::create_rustls_context(
2138                &config,
2139                self.resolver.clone(),
2140            )?))
2141        } else {
2142            None
2143        };
2144
2145        // HTTP answers: merge legacy `http_answers` and the new `answers`
2146        // map on top of the candidate config, then build the listener-level
2147        // template registry from it. It is fallible, so it is built here and
2148        // published below with the config, never before.
2149        let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
2150        let rebuilt = if answers_changed {
2151            if let Some(ref new_answers) = patch.http_answers {
2152                crate::sozu_command::state::merge_custom_http_answers(
2153                    &mut config.http_answers,
2154                    new_answers,
2155                );
2156            }
2157            for (code, body) in &patch.answers {
2158                if !body.is_empty() {
2159                    config.answers.insert(code.clone(), body.clone());
2160                }
2161            }
2162
2163            let mut answers_map = config.answers.clone();
2164            if let Some(ref legacy) = config.http_answers {
2165                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
2166            }
2167            Some(
2168                HttpAnswers::new(&answers_map)
2169                    .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?,
2170            )
2171        } else {
2172            None
2173        };
2174
2175        // Commit: nothing below can fail.
2176        self.config = config;
2177        if let Some(new_rustls) = new_rustls {
2178            self.rustls_details = new_rustls;
2179        }
2180        if let Some(alpn_wrapper) = &patch.alpn_protocols {
2181            // Post: the live config must now name exactly the patched ALPN
2182            // set. New handshakes negotiate against this set, so the
2183            // `upgrade_handshake` "protocol ∈ configured ALPN" property is
2184            // anchored to what we just stored.
2185            debug_assert_eq!(
2186                self.config.alpn_protocols, alpn_wrapper.values,
2187                "committed ALPN config must match the patch values exactly"
2188            );
2189        }
2190        if let Some(mut rebuilt) = rebuilt {
2191            // The rebuilt registry is PUBLISHED under a new `Rc`, not written
2192            // through the old one. Every request already in flight captured
2193            // the old handle when it arrived (`mux::Context::create_stream`)
2194            // and renders its default answers from that, so a reload landing
2195            // mid-request cannot change the page that request is about to
2196            // emit; requests arriving after this point capture the new handle.
2197            // That is the one-request staleness window of
2198            // `lib/src/protocol/mux/LIFECYCLE.md` §2.5.
2199            //
2200            // The per-cluster overrides are therefore COPIED, not
2201            // `mem::take`n: taking them would empty the registry those
2202            // in-flight requests still hold and strip their cluster templates
2203            // mid-response. The copy is a map clone over `Rc<Template>`, so
2204            // both registries share the compiled templates themselves.
2205            rebuilt.cluster_answers = self.answers.borrow().cluster_answers.clone();
2206            self.answers = Rc::new(RefCell::new(rebuilt));
2207        }
2208
2209        // HSTS: full-object replacement when present in the patch (stored
2210        // into the candidate config above; a block without `enabled` was
2211        // refused before the first write). Absent patch field preserves
2212        // current value (matches the rest of this partial-update handler).
2213        //
2214        // Inheriting frontends are refreshed in place via
2215        // `Router::refresh_inheriting_hsts`: every frontend that declared no
2216        // `hsts` of its own at add time
2217        // (`Frontend.inherits_listener_hsts == true`) gets its
2218        // `headers_response` re-materialised against the new value.
2219        // Explicit per-frontend overrides
2220        // (`inherits_listener_hsts == false`) are untouched. The
2221        // `http.hsts.listener_default_patched` counter still fires so
2222        // dashboards can correlate patches with the new
2223        // `http.hsts.frontend_refreshed` counter (sum of refreshed
2224        // frontends from this patch).
2225        if patch.hsts.is_some() {
2226            let refreshed = self
2227                .fronts
2228                .refresh_inheriting_hsts(self.config.hsts.as_ref());
2229            for _ in 0..refreshed {
2230                crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
2231            }
2232            info!(
2233                "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
2234                 frontend(s). Explicit per-frontend overrides untouched.",
2235                log_module_context!(),
2236                self.config.address,
2237                refreshed,
2238            );
2239            crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
2240        }
2241
2242        Ok(())
2243    }
2244
2245    pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2246        self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
2247    }
2248
2249    /// Variant of [`Self::add_https_front`] that records the origin of
2250    /// `tls_front.hsts` so listener-default patches can reflow inheriting
2251    /// frontends without disturbing explicit per-frontend overrides. The
2252    /// caller passes [`crate::router::HstsOrigin::InheritedFromListenerDefault`] when
2253    /// the value was filled in from `self.config.hsts` rather than from
2254    /// the operator's per-frontend configuration.
2255    pub fn add_https_front_with_hsts_origin(
2256        &mut self,
2257        tls_front: HttpFrontend,
2258        hsts_origin: crate::router::HstsOrigin,
2259    ) -> Result<(), ListenerError> {
2260        self.fronts
2261            .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
2262            .map_err(ListenerError::AddFrontend)
2263    }
2264
2265    pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2266        debug!(
2267            "{} removing tls_front {:?}",
2268            log_module_context!(),
2269            tls_front
2270        );
2271        self.fronts
2272            .remove_http_front(&tls_front)
2273            .map_err(ListenerError::RemoveFrontend)
2274    }
2275
2276    fn accept(&mut self) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2277        if let Some(ref sock) = self.listener {
2278            match sock.accept() {
2279                Ok((frontend_sock, peer)) => {
2280                    if self.nodelay_backlog
2281                        && let Err(e) = frontend_sock.set_nodelay(true)
2282                    {
2283                        error!(
2284                            "{} error setting nodelay on front socket({:?}): {:?}",
2285                            log_module_context!(),
2286                            frontend_sock,
2287                            e
2288                        );
2289                    }
2290                    Ok((frontend_sock, peer))
2291                }
2292                Err(e) if e.kind() == ErrorKind::WouldBlock => {
2293                    // The backlog queued before `activate()` is drained.
2294                    self.nodelay_backlog = false;
2295                    Err(AcceptError::WouldBlock)
2296                }
2297                Err(e) => {
2298                    error!("{} accept() IO error: {:?}", log_module_context!(), e);
2299                    Err(AcceptError::IoError)
2300                }
2301            }
2302        } else {
2303            error!(
2304                "{} cannot accept connections, no listening socket available",
2305                log_module_context!()
2306            );
2307            Err(AcceptError::IoError)
2308        }
2309    }
2310}
2311
2312pub struct HttpsProxy {
2313    listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
2314    clusters: HashMap<ClusterId, Cluster>,
2315    backends: Rc<RefCell<BackendMap>>,
2316    pool: Rc<RefCell<Pool>>,
2317    registry: Registry,
2318    sessions: Rc<RefCell<SessionManager>>,
2319}
2320
2321impl HttpsProxy {
2322    pub fn new(
2323        registry: Registry,
2324        sessions: Rc<RefCell<SessionManager>>,
2325        pool: Rc<RefCell<Pool>>,
2326        backends: Rc<RefCell<BackendMap>>,
2327    ) -> HttpsProxy {
2328        HttpsProxy {
2329            listeners: HashMap::new(),
2330            clusters: HashMap::new(),
2331            backends,
2332            pool,
2333            registry,
2334            sessions,
2335        }
2336    }
2337
2338    pub fn add_listener(
2339        &mut self,
2340        config: HttpsListenerConfig,
2341        token: Token,
2342    ) -> Result<Token, ProxyError> {
2343        if self.listeners.contains_key(&token) {
2344            return Err(ProxyError::ListenerAlreadyPresent);
2345        }
2346        let mut https_listener =
2347            HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
2348        // Frontends and certificates name their listener by address alone, so
2349        // a listener added next to one already on its address (on another
2350        // interface) takes over the routes and certificates that one serves:
2351        // every listener on an address holds the same ones, whenever it was
2352        // added (sozu-proxy/sozu#719). The certificates are copied into this
2353        // listener's own resolver, the one its rustls configuration already
2354        // points at, so later changes keep reaching it through the fan-out.
2355        if let Some(sibling) = self
2356            .listeners_at(https_listener.address)
2357            .ok()
2358            .and_then(|listeners| listeners.into_iter().next())
2359        {
2360            let sibling = sibling.borrow();
2361            https_listener.fronts = sibling.fronts.clone();
2362            // Frontends that declared no `hsts` of their own inherit this
2363            // listener's default instead of the sibling's, whether or not the
2364            // sibling had one (sozu-proxy/sozu#1691).
2365            https_listener
2366                .fronts
2367                .refresh_inheriting_hsts(https_listener.config.hsts.as_ref());
2368            https_listener.tags = sibling.tags.clone();
2369            let certificates = sibling
2370                .resolver
2371                .0
2372                .lock()
2373                .map_err(|e| ProxyError::Lock(e.to_string()))?
2374                .clone();
2375            *https_listener
2376                .resolver
2377                .0
2378                .lock()
2379                .map_err(|e| ProxyError::Lock(e.to_string()))? = certificates;
2380        }
2381        match self.listeners.entry(token) {
2382            Entry::Vacant(entry) => {
2383                entry.insert(Rc::new(RefCell::new(https_listener)));
2384                Ok(token)
2385            }
2386            _ => Err(ProxyError::ListenerAlreadyPresent),
2387        }
2388    }
2389
2390    /// Add one listener and restore routes and certificates retained in the
2391    /// worker state when no live sibling can provide them.
2392    ///
2393    /// A sibling already carries the address-wide state and `add_listener`
2394    /// clones it. When the last listener on an address was removed, replay the
2395    /// retained objects onto the new listener before the server commits its
2396    /// session slot. Any failure drops only that new listener and its resolver.
2397    pub(crate) fn add_listener_with_retained_state(
2398        &mut self,
2399        config: HttpsListenerConfig,
2400        token: Token,
2401        retained_frontends: Vec<RequestHttpFrontend>,
2402        retained_certificates: Vec<AddCertificate>,
2403    ) -> Result<Token, ProxyError> {
2404        let address = config.address.into();
2405        let had_sibling = self.listeners_at(address).is_ok();
2406        self.add_listener(config, token)?;
2407
2408        if !had_sibling {
2409            for frontend in retained_frontends {
2410                if let Err(error) = self.add_https_frontend(frontend) {
2411                    self.listeners.remove(&token);
2412                    return Err(error);
2413                }
2414            }
2415            for certificate in retained_certificates {
2416                if let Err(error) = self.add_certificate(certificate) {
2417                    self.listeners.remove(&token);
2418                    return Err(error);
2419                }
2420            }
2421        }
2422
2423        Ok(token)
2424    }
2425
2426    pub fn remove_listener(
2427        &mut self,
2428        remove: RemoveListener,
2429    ) -> Result<Option<ResponseContent>, ProxyError> {
2430        let len = self.listeners.len();
2431
2432        let remove_address = remove.listener_key();
2433        self.listeners
2434            .retain(|_, listener| !listener.borrow().is(&remove_address));
2435
2436        if !self.listeners.len() < len {
2437            info!(
2438                "{} no HTTPS listener to remove at address {}",
2439                log_module_context!(),
2440                remove_address
2441            )
2442        }
2443        Ok(None)
2444    }
2445
2446    pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
2447        let listeners = std::mem::take(&mut self.listeners);
2448        let mut socket_errors = vec![];
2449        for l in listeners.values() {
2450            if let Some(mut sock) = l.borrow_mut().listener.take() {
2451                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2452                if let Err(e) = self.registry.deregister(&mut sock) {
2453                    let error = format!("socket {sock:?}: {e:?}");
2454                    socket_errors.push(error);
2455                }
2456            }
2457        }
2458
2459        if !socket_errors.is_empty() {
2460            return Err(ProxyError::SoftStop {
2461                proxy_protocol: "HTTPS".to_string(),
2462                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2463            });
2464        }
2465
2466        Ok(())
2467    }
2468
2469    pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
2470        let mut listeners = std::mem::take(&mut self.listeners);
2471        let mut socket_errors = vec![];
2472        for (_, l) in listeners.drain() {
2473            if let Some(mut sock) = l.borrow_mut().listener.take() {
2474                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2475                if let Err(e) = self.registry.deregister(&mut sock) {
2476                    let error = format!("socket {sock:?}: {e:?}");
2477                    socket_errors.push(error);
2478                }
2479            }
2480        }
2481
2482        if !socket_errors.is_empty() {
2483            return Err(ProxyError::HardStop {
2484                proxy_protocol: "HTTPS".to_string(),
2485                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2486            });
2487        }
2488
2489        Ok(())
2490    }
2491
2492    pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
2493        let certificates = self
2494            .listeners
2495            .values()
2496            .map(|listener| {
2497                let owned = listener.borrow();
2498                // Same handling as the `add`/`remove`/`replace` siblings: a
2499                // poisoned resolver is reported as `ProxyError::Lock`, never
2500                // unwrapped. A read-only query must not be the one control-
2501                // plane request that kills the worker.
2502                let resolver = owned
2503                    .resolver
2504                    .0
2505                    .lock()
2506                    .map_err(|e| ProxyError::Lock(e.to_string()))?;
2507                let certificate_summaries = resolver
2508                    .domains
2509                    .to_hashmap()
2510                    .drain()
2511                    .map(|(k, fingerprint)| CertificateSummary {
2512                        domain: certificate_summary_domain(&k),
2513                        fingerprint: fingerprint.to_string(),
2514                    })
2515                    .collect();
2516
2517                Ok(CertificatesByAddress {
2518                    address: owned.address.into(),
2519                    certificate_summaries,
2520                })
2521            })
2522            .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2523
2524        let listeners_count = certificates.len();
2525        let certificates_count = certificates
2526            .iter()
2527            .map(|entry| entry.certificate_summaries.len())
2528            .fold(0usize, |total, count| total.saturating_add(count));
2529
2530        info!(
2531            "{} got Certificates::All query, listeners_count={} certificates_count={}",
2532            log_module_context!(),
2533            listeners_count,
2534            certificates_count,
2535        );
2536
2537        Ok(Some(
2538            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2539        ))
2540    }
2541
2542    pub fn query_certificate_for_domain(
2543        &mut self,
2544        domain: String,
2545    ) -> Result<Option<ResponseContent>, ProxyError> {
2546        // The resolver keys its SNI trie on ASCII-lowercased certificate
2547        // names (`CertifiedKeyWrapper::try_from`), because that is the
2548        // only form rustls ever asks it for. An operator query has to
2549        // ask the same question a handshake does, or
2550        // `sozu query certificates --domain Example.COM` reports the
2551        // certificate missing while the proxy happily serves it.
2552        let domain_key = domain.to_ascii_lowercase();
2553        let certificates = self
2554            .listeners
2555            .values()
2556            .map(|listener| {
2557                let owned = listener.borrow();
2558                // See `query_all_certificates`: lock like the
2559                // `add`/`remove`/`replace` siblings do.
2560                let resolver = owned
2561                    .resolver
2562                    .0
2563                    .lock()
2564                    .map_err(|e| ProxyError::Lock(e.to_string()))?;
2565                let mut certificate_summaries = vec![];
2566
2567                if let Some((k, fingerprint)) = resolver.domain_lookup(domain_key.as_bytes(), true)
2568                {
2569                    certificate_summaries.push(CertificateSummary {
2570                        domain: certificate_summary_domain(k),
2571                        fingerprint: fingerprint.to_string(),
2572                    });
2573                }
2574                Ok(CertificatesByAddress {
2575                    address: owned.address.into(),
2576                    certificate_summaries,
2577                })
2578            })
2579            .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2580
2581        let listeners_count = certificates.len();
2582        let certificates_count = certificates
2583            .iter()
2584            .map(|entry| entry.certificate_summaries.len())
2585            .fold(0usize, |total, count| total.saturating_add(count));
2586
2587        info!(
2588            "{} got Certificates::Domain query, domain_bytes={} listeners_count={} certificates_count={}",
2589            log_module_context!(),
2590            domain.len(),
2591            listeners_count,
2592            certificates_count,
2593        );
2594
2595        Ok(Some(
2596            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2597        ))
2598    }
2599
2600    /// What this proxy would do with a socket handed to
2601    /// [`activate_listener`](Self::activate_listener) for `addr`. See
2602    /// [`crate::InheritedSocketFate`].
2603    ///
2604    /// The event loop is single-threaded, so nothing can change the listener's
2605    /// `active` flag between this answer and the `activate_listener` call that
2606    /// acts on it.
2607    pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2608        use crate::InheritedSocketFate;
2609        match self
2610            .listeners
2611            .values()
2612            .find(|listener| listener.borrow().is(addr))
2613        {
2614            None => InheritedSocketFate::Unclaimed,
2615            Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2616            Some(_) => InheritedSocketFate::Adopted,
2617        }
2618    }
2619
2620    pub fn activate_listener(
2621        &mut self,
2622        addr: &ListenerKey,
2623        tcp_listener: Option<MioTcpListener>,
2624    ) -> Result<Token, ProxyError> {
2625        let listener = self
2626            .listeners
2627            .values()
2628            .find(|listener| listener.borrow().is(addr))
2629            .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2630
2631        listener
2632            .borrow_mut()
2633            .activate(&self.registry, tcp_listener)
2634            .map_err(|listener_error| ProxyError::ListenerActivation {
2635                address: addr.address,
2636                listener_error,
2637            })
2638    }
2639
2640    /// The slab token reserved for the listener at `address`, if this proxy
2641    /// holds one.
2642    ///
2643    /// A listener owns exactly one slab slot for its whole `AddListener` ->
2644    /// `RemoveListener` lifetime, and no `remove_listener` implementation
2645    /// touches the session slab. `Server` therefore reads the token here
2646    /// BEFORE dropping the listener, so that reserved slot is released exactly
2647    /// once, at the end of the lifetime.
2648    pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
2649        self.listeners
2650            .iter()
2651            .find(|(_, listener)| listener.borrow().is(address))
2652            .map(|(token, _)| *token)
2653    }
2654
2655    pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
2656        self.listeners
2657            .values()
2658            .filter_map(|listener| {
2659                let mut owned = listener.borrow_mut();
2660                if let Some(listener) = owned.listener.take() {
2661                    // Reset `active` so a subsequent `activate()` re-binds
2662                    // instead of short-circuiting on the stale flag.
2663                    owned.active = false;
2664                    return Some((owned.key(), listener));
2665                }
2666
2667                None
2668            })
2669            .collect()
2670    }
2671
2672    pub fn give_back_listener(
2673        &mut self,
2674        address: &ListenerKey,
2675    ) -> Result<(Token, MioTcpListener), ProxyError> {
2676        let listener = self
2677            .listeners
2678            .values()
2679            .find(|listener| listener.borrow().is(address))
2680            .ok_or(ProxyError::NoListenerFound(address.clone()))?;
2681
2682        let mut owned = listener.borrow_mut();
2683
2684        let taken_listener = owned
2685            .listener
2686            .take()
2687            .ok_or(ProxyError::UnactivatedListener)?;
2688
2689        // Reset `active` so a subsequent `activate()` re-binds instead of
2690        // short-circuiting on the stale flag.
2691        owned.active = false;
2692
2693        Ok((owned.token, taken_listener))
2694    }
2695
2696    /// Every listener on `address`, whatever its interface. Frontends and
2697    /// certificates name their listener by address alone, so they apply to
2698    /// all the listeners sharing that address on different interfaces.
2699    fn listeners_at(
2700        &self,
2701        address: StdSocketAddr,
2702    ) -> Result<Vec<Rc<RefCell<HttpsListener>>>, ProxyError> {
2703        let mut listeners: Vec<_> = self
2704            .listeners
2705            .iter()
2706            .filter(|(_, listener)| listener.borrow().address == address)
2707            .map(|(token, listener)| (*token, listener.clone()))
2708            .collect();
2709        // Token order, so a failure is reported the same way on every run.
2710        listeners.sort_by_key(|(token, _)| *token);
2711        let listeners: Vec<_> = listeners
2712            .into_iter()
2713            .map(|(_, listener)| listener)
2714            .collect();
2715        if listeners.is_empty() {
2716            return Err(ProxyError::NoListenerFound(address.into()));
2717        }
2718        Ok(listeners)
2719    }
2720
2721    /// Apply a partial-update patch to the identified HTTPS listener.
2722    pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
2723        let address: std::net::SocketAddr = patch.address.into();
2724        let listener = self
2725            .listeners
2726            .values()
2727            .find(|l| l.borrow().address == address)
2728            .ok_or(ProxyError::NoListenerFound(address.into()))?;
2729        listener
2730            .borrow_mut()
2731            .update_config(&patch)
2732            .map_err(|listener_error| ProxyError::ListenerActivation {
2733                address,
2734                listener_error,
2735            })
2736    }
2737
2738    pub fn add_cluster(
2739        &mut self,
2740        mut cluster: Cluster,
2741    ) -> Result<Option<ResponseContent>, ProxyError> {
2742        let mut cluster_overrides = cluster.answers.clone();
2743        if let Some(answer_503) = cluster.answer_503.take() {
2744            cluster_overrides
2745                .entry("503".to_owned())
2746                .or_insert(answer_503);
2747        }
2748        if !cluster_overrides.is_empty() {
2749            for listener in self.listeners.values() {
2750                listener
2751                    .borrow()
2752                    .answers
2753                    .borrow_mut()
2754                    .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
2755                    .map_err(|(status, error)| {
2756                        ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
2757                    })?;
2758            }
2759        }
2760        self.clusters
2761            .insert(cluster.cluster_id.as_str().into(), cluster);
2762        Ok(None)
2763    }
2764
2765    pub fn remove_cluster(
2766        &mut self,
2767        cluster_id: &str,
2768    ) -> Result<Option<ResponseContent>, ProxyError> {
2769        self.clusters.remove(cluster_id);
2770        for listener in self.listeners.values() {
2771            listener
2772                .borrow()
2773                .answers
2774                .borrow_mut()
2775                .remove_cluster_answers(cluster_id);
2776        }
2777
2778        Ok(None)
2779    }
2780
2781    pub fn add_https_frontend(
2782        &mut self,
2783        front: RequestHttpFrontend,
2784    ) -> Result<Option<ResponseContent>, ProxyError> {
2785        let front = front.clone().to_frontend().map_err(|request_error| {
2786            ProxyError::WrongInputFrontend {
2787                front: Box::new(front),
2788                error: request_error.to_string(),
2789            }
2790        })?;
2791
2792        for listener in self.listeners_at(front.address)? {
2793            let mut listener = listener.borrow_mut();
2794            let mut front = front.clone();
2795
2796            // ── HSTS listener-default → frontend inheritance ─────────────────
2797            // When the frontend declares no `hsts` block, fall back to the
2798            // listener default so the operator can opt into HSTS once at the
2799            // listener and have every HTTPS frontend inherit it.
2800            // `enabled = Some(false)` on the frontend is the explicit-disable
2801            // signal: it stays as-is and suppresses the inherited default.
2802            //
2803            // The `hsts_origin` flag records the frontend's own intent — it
2804            // declared no `hsts` — rather than whether a default existed, so
2805            // the resulting `Frontend` carries the inheritance bit even when
2806            // the listener has no default yet. A later
2807            // `UpdateHttpsListenerConfig.hsts` patch, or the copy into a
2808            // listener added on the same address (`HttpsProxy::add_listener`), then
2809            // resolves it against that listener's own default via
2810            // `Router::refresh_inheriting_hsts` without disturbing explicit
2811            // per-frontend overrides (sozu-proxy/sozu#1691).
2812            let hsts_origin = if front.hsts.is_none() {
2813                front.hsts = listener.config.hsts;
2814                crate::router::HstsOrigin::InheritedFromListenerDefault
2815            } else {
2816                crate::router::HstsOrigin::Explicit
2817            };
2818
2819            listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
2820            listener
2821                .add_https_front_with_hsts_origin(front, hsts_origin)
2822                .map_err(ProxyError::AddFrontend)?;
2823        }
2824        Ok(None)
2825    }
2826
2827    pub fn remove_https_frontend(
2828        &mut self,
2829        front: RequestHttpFrontend,
2830    ) -> Result<Option<ResponseContent>, ProxyError> {
2831        let front = front.clone().to_frontend().map_err(|request_error| {
2832            ProxyError::WrongInputFrontend {
2833                front: Box::new(front),
2834                error: request_error.to_string(),
2835            }
2836        })?;
2837
2838        // Every listener on the address drops the route, even when another
2839        // one fails: a sibling must never keep a route the others removed.
2840        // The first failure, in token order, is the one reported.
2841        let mut result = Ok(None);
2842        for listener in self.listeners_at(front.address)? {
2843            let mut listener = listener.borrow_mut();
2844            let hostname = front.hostname.to_owned();
2845
2846            if let Err(error) = listener.remove_https_front(front.clone())
2847                && result.is_ok()
2848            {
2849                result = Err(ProxyError::RemoveFrontend(error));
2850            }
2851
2852            if !listener.fronts.has_hostname(&hostname) {
2853                listener.set_tags(hostname, None);
2854            }
2855        }
2856        result
2857    }
2858
2859    pub fn add_certificate(
2860        &mut self,
2861        add_certificate: AddCertificate,
2862    ) -> Result<Option<ResponseContent>, ProxyError> {
2863        let address = add_certificate.address.into();
2864
2865        for listener in self.listeners_at(address)? {
2866            let listener = listener.borrow();
2867            let mut resolver = listener
2868                .resolver
2869                .0
2870                .lock()
2871                .map_err(|e| ProxyError::Lock(e.to_string()))?;
2872
2873            resolver
2874                .add_certificate(&add_certificate)
2875                .map_err(ProxyError::AddCertificate)?;
2876        }
2877
2878        Ok(None)
2879    }
2880
2881    //FIXME: should return an error if certificate still has fronts referencing it
2882    pub fn remove_certificate(
2883        &mut self,
2884        remove_certificate: RemoveCertificate,
2885    ) -> Result<Option<ResponseContent>, ProxyError> {
2886        let address = remove_certificate.address.into();
2887
2888        let fingerprint = Fingerprint(
2889            hex::decode(&remove_certificate.fingerprint)
2890                .map_err(ProxyError::WrongCertificateFingerprint)?,
2891        );
2892
2893        // Every listener on the address applies the change, even when another
2894        // one fails, so none keeps a certificate its siblings dropped; the
2895        // first failure, in token order, is the one reported.
2896        let mut result = Ok(None);
2897        for listener in self.listeners_at(address)? {
2898            let listener = listener.borrow();
2899            let applied = listener
2900                .resolver
2901                .0
2902                .lock()
2903                .map_err(|e| ProxyError::Lock(e.to_string()))
2904                .and_then(|mut resolver| {
2905                    resolver
2906                        .remove_certificate(&fingerprint)
2907                        .map_err(ProxyError::RemoveCertificate)
2908                });
2909            if let Err(error) = applied
2910                && result.is_ok()
2911            {
2912                result = Err(error);
2913            }
2914        }
2915        result
2916    }
2917
2918    //FIXME: should return an error if certificate still has fronts referencing it
2919    pub fn replace_certificate(
2920        &mut self,
2921        replace_certificate: ReplaceCertificate,
2922    ) -> Result<Option<ResponseContent>, ProxyError> {
2923        let address = replace_certificate.address.into();
2924
2925        // Every listener on the address applies the change, even when another
2926        // one fails, so none keeps a certificate its siblings dropped; the
2927        // first failure, in token order, is the one reported.
2928        let mut result = Ok(None);
2929        for listener in self.listeners_at(address)? {
2930            let listener = listener.borrow();
2931            let applied = listener
2932                .resolver
2933                .0
2934                .lock()
2935                .map_err(|e| ProxyError::Lock(e.to_string()))
2936                .and_then(|mut resolver| {
2937                    resolver
2938                        .replace_certificate(&replace_certificate)
2939                        .map_err(ProxyError::ReplaceCertificate)
2940                });
2941            if let Err(error) = applied
2942                && result.is_ok()
2943            {
2944                result = Err(error);
2945            }
2946        }
2947        result
2948    }
2949}
2950
2951impl ProxyConfiguration for HttpsProxy {
2952    fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2953        match self.listeners.get(&Token(token.0)) {
2954            Some(listener) => listener.borrow_mut().accept(),
2955            None => Err(AcceptError::IoError),
2956        }
2957    }
2958
2959    fn create_session(
2960        &mut self,
2961        mut frontend_sock: MioTcpStream,
2962        peer: StdSocketAddr,
2963        token: ListenToken,
2964        wait_time: Duration,
2965        proxy: Rc<RefCell<Self>>,
2966    ) -> Result<(), AcceptError> {
2967        let listener = self
2968            .listeners
2969            .get(&Token(token.0))
2970            .ok_or(AcceptError::IoError)?;
2971        // No `set_nodelay` here: the socket has `TCP_NODELAY` from its
2972        // listener, or from `HttpsListener::accept` if it was queued before
2973        // the listener set it.
2974
2975        let owned = listener.borrow();
2976        let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
2977            error!(
2978                "{} failed to create server session: {:?}",
2979                log_module_context!(),
2980                e
2981            );
2982            AcceptError::IoError
2983        })?;
2984
2985        let mut session_manager = self.sessions.borrow_mut();
2986        let entry = session_manager.slab.vacant_entry();
2987        let session_token = Token(entry.key());
2988        // The session token IS the slab key: the event loop later indexes the
2989        // slab directly by the mio `Token` it receives, so any divergence here
2990        // would route readiness to the wrong session slot. Snapshot the key to
2991        // re-check after `entry.insert` consumes the entry.
2992        debug_assert_eq!(
2993            session_token.0,
2994            entry.key(),
2995            "HTTPS session token must equal its slab key"
2996        );
2997
2998        self.registry
2999            .register(
3000                &mut frontend_sock,
3001                session_token,
3002                Interest::READABLE | Interest::WRITABLE,
3003            )
3004            .map_err(|register_error| {
3005                error!(
3006                    "{} error registering front socket({:?}): {:?}",
3007                    log_module_context!(),
3008                    frontend_sock,
3009                    register_error
3010                );
3011                AcceptError::RegisterError
3012            })?;
3013
3014        let public_address: StdSocketAddr = match owned.config.public_address {
3015            Some(pub_addr) => pub_addr.into(),
3016            None => owned.config.address.into(),
3017        };
3018
3019        let session = Rc::new(RefCell::new(HttpsSession::new(
3020            Duration::from_secs(owned.config.back_timeout as u64),
3021            Duration::from_secs(owned.config.connect_timeout as u64),
3022            Duration::from_secs(owned.config.front_timeout as u64),
3023            Duration::from_secs(owned.config.request_timeout as u64),
3024            owned.config.expect_proxy,
3025            listener.clone(),
3026            Rc::downgrade(&self.pool),
3027            proxy,
3028            public_address,
3029            rustls_details,
3030            frontend_sock,
3031            peer,
3032            session_token,
3033            wait_time,
3034        )));
3035        // The freshly built session must own exactly the token it is filed under,
3036        // so event-loop dispatch (slab key → session) and self-removal
3037        // (`frontend_token()` → slab key) agree.
3038        debug_assert_eq!(
3039            session.borrow().frontend_token(),
3040            session_token,
3041            "stored HTTPS session must report the slab token as its frontend token"
3042        );
3043        entry.insert(session);
3044
3045        Ok(())
3046    }
3047
3048    fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
3049        let request_id = request.id.clone();
3050
3051        let request_type = match request.content.request_type {
3052            Some(t) => t,
3053            None => return WorkerResponse::error(request_id, "Empty request"),
3054        };
3055
3056        let content_result = match request_type {
3057            RequestType::AddCluster(cluster) => {
3058                debug!(
3059                    "{} {} add cluster {:?}",
3060                    log_module_context!(),
3061                    request_id,
3062                    cluster
3063                );
3064                self.add_cluster(cluster)
3065            }
3066            RequestType::RemoveCluster(cluster_id) => {
3067                debug!(
3068                    "{} {} remove cluster {:?}",
3069                    log_module_context!(),
3070                    request_id,
3071                    cluster_id
3072                );
3073                self.remove_cluster(&cluster_id)
3074            }
3075            RequestType::AddHttpsFrontend(front)
3076            | RequestType::AddHttpsFrontendWithSilentClose(front)
3077            | RequestType::AddHttpsFrontendWithHeaderPredicates(front) => {
3078                debug!(
3079                    "{} {} add https front {:?}",
3080                    log_module_context!(),
3081                    request_id,
3082                    front
3083                );
3084                self.add_https_frontend(front)
3085            }
3086            RequestType::RemoveHttpsFrontend(front)
3087            | RequestType::RemoveHttpsFrontendWithHeaderPredicates(front) => {
3088                debug!(
3089                    "{} {} remove https front {:?}",
3090                    log_module_context!(),
3091                    request_id,
3092                    front
3093                );
3094                self.remove_https_frontend(front)
3095            }
3096            RequestType::AddCertificate(add_certificate) => {
3097                debug!(
3098                    "{} {} add certificate: {:?}",
3099                    log_module_context!(),
3100                    request_id,
3101                    add_certificate
3102                );
3103                self.add_certificate(add_certificate)
3104            }
3105            RequestType::RemoveCertificate(remove_certificate) => {
3106                debug!(
3107                    "{} {} remove certificate: {:?}",
3108                    log_module_context!(),
3109                    request_id,
3110                    remove_certificate
3111                );
3112                self.remove_certificate(remove_certificate)
3113            }
3114            RequestType::ReplaceCertificate(replace_certificate) => {
3115                debug!(
3116                    "{} {} replace certificate: {:?}",
3117                    log_module_context!(),
3118                    request_id,
3119                    replace_certificate
3120                );
3121                self.replace_certificate(replace_certificate)
3122            }
3123            RequestType::RemoveListener(remove) => {
3124                debug!(
3125                    "{} removing HTTPS listener at address {:?}",
3126                    log_module_context!(),
3127                    remove.address
3128                );
3129                self.remove_listener(remove)
3130            }
3131            RequestType::SoftStop(_) => {
3132                debug!(
3133                    "{} {} processing soft shutdown",
3134                    log_module_context!(),
3135                    request_id
3136                );
3137                match self.soft_stop() {
3138                    Ok(_) => {
3139                        info!(
3140                            "{} {} soft stop successful",
3141                            log_module_context!(),
3142                            request_id
3143                        );
3144                        return WorkerResponse::processing(request.id);
3145                    }
3146                    Err(e) => Err(e),
3147                }
3148            }
3149            RequestType::HardStop(_) => {
3150                debug!(
3151                    "{} {} processing hard shutdown",
3152                    log_module_context!(),
3153                    request_id
3154                );
3155                match self.hard_stop() {
3156                    Ok(_) => {
3157                        debug!(
3158                            "{} {} hard stop successful",
3159                            log_module_context!(),
3160                            request_id
3161                        );
3162                        return WorkerResponse::processing(request.id);
3163                    }
3164                    Err(e) => Err(e),
3165                }
3166            }
3167            RequestType::Status(_) => {
3168                debug!("{} {} status", log_module_context!(), request_id);
3169                Ok(None)
3170            }
3171            RequestType::QueryCertificatesFromWorkers(filters) => {
3172                if let Some(domain) = filters.domain {
3173                    debug!(
3174                        "{} {} query certificate for domain_bytes={}",
3175                        log_module_context!(),
3176                        request_id,
3177                        domain.len(),
3178                    );
3179                    self.query_certificate_for_domain(domain)
3180                } else {
3181                    debug!(
3182                        "{} {} query all certificates",
3183                        log_module_context!(),
3184                        request_id
3185                    );
3186                    self.query_all_certificates()
3187                }
3188            }
3189            other_request => {
3190                debug!(
3191                    "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
3192                    log_module_context!(),
3193                    request.id,
3194                    other_request
3195                );
3196                Err(ProxyError::UnsupportedMessage)
3197            }
3198        };
3199
3200        match content_result {
3201            Ok(content) => {
3202                debug!("{} {} successful", log_module_context!(), request_id);
3203                match content {
3204                    Some(content) => WorkerResponse::ok_with_content(request_id, content),
3205                    None => WorkerResponse::ok(request_id),
3206                }
3207            }
3208            Err(proxy_error) => {
3209                debug!(
3210                    "{} {} unsuccessful: {}",
3211                    log_module_context!(),
3212                    request_id,
3213                    proxy_error
3214                );
3215                WorkerResponse::error(request_id, proxy_error)
3216            }
3217        }
3218    }
3219}
3220impl L7Proxy for HttpsProxy {
3221    fn kind(&self) -> ListenerType {
3222        ListenerType::Https
3223    }
3224
3225    fn register_socket(
3226        &self,
3227        socket: &mut MioTcpStream,
3228        token: Token,
3229        interest: Interest,
3230    ) -> Result<(), std::io::Error> {
3231        self.registry.register(socket, token, interest)
3232    }
3233
3234    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
3235        let mut session_manager = self.sessions.borrow_mut();
3236        let entry = session_manager.slab.vacant_entry();
3237        let token = Token(entry.key());
3238        let _entry = entry.insert(session);
3239        token
3240    }
3241
3242    fn remove_session(&self, token: Token) -> bool {
3243        let mut sessions = self.sessions.borrow_mut();
3244        // Mirror of HttpProxy::remove_session — drain the per-(cluster,
3245        // source-IP) accounting before the slab slot is reused.
3246        sessions.untrack_all_cluster_ip(token);
3247        sessions.slab.try_remove(token.0).is_some()
3248    }
3249
3250    fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
3251        &self.clusters
3252    }
3253
3254    fn sessions(&self) -> Rc<RefCell<SessionManager>> {
3255        self.sessions.clone()
3256    }
3257}
3258
3259/// Used for metrics keeping
3260fn rustls_version_str(version: ProtocolVersion) -> &'static str {
3261    match version {
3262        ProtocolVersion::SSLv2 => "tls.version.SSLv2",
3263        ProtocolVersion::SSLv3 => "tls.version.SSLv3",
3264        ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
3265        ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
3266        ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
3267        ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
3268        ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
3269        ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
3270        ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
3271        ProtocolVersion::Unknown(_) => "tls.version.Unknown",
3272        _ => "tls.version.unimplemented",
3273    }
3274}
3275
3276/// Short label suitable for access logs (e.g. `"TLSv1.3"`).
3277///
3278/// Distinct from [`rustls_version_str`] which prefixes with `tls.version.`
3279/// for metric ingestion. Returns `None` for variants Sōzu does not know how
3280/// to label, so the access log records `tls_version` as absent rather than
3281/// emitting a misleading `"unimplemented"` literal.
3282pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
3283    match version {
3284        ProtocolVersion::SSLv2 => Some("SSLv2"),
3285        ProtocolVersion::SSLv3 => Some("SSLv3"),
3286        ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
3287        ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
3288        ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
3289        ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
3290        ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
3291        ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
3292        ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
3293        _ => None,
3294    }
3295}
3296
3297/// Used for metrics keeping
3298fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
3299    match cipher.suite() {
3300        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3301            "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
3302        }
3303        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3304            "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
3305        }
3306        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3307            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
3308        }
3309        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3310            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
3311        }
3312        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3313            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
3314        }
3315        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3316            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
3317        }
3318        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
3319        CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
3320        CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
3321        _ => "tls.cipher.Unsupported",
3322    }
3323}
3324
3325/// Short label suitable for access logs (e.g. `"TLS_AES_128_GCM_SHA256"`).
3326///
3327/// Distinct from [`rustls_ciphersuite_str`] which prefixes with `tls.cipher.`
3328/// for metric ingestion. Returns `None` for cipher suites Sōzu does not know
3329/// how to label, so the access log records `tls_cipher` as absent rather
3330/// than emitting a misleading `"Unsupported"` literal.
3331pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
3332    match cipher.suite() {
3333        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3334            Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
3335        }
3336        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3337            Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
3338        }
3339        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3340            Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
3341        }
3342        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3343            Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
3344        }
3345        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3346            Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
3347        }
3348        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3349            Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
3350        }
3351        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
3352        CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
3353        CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
3354        _ => None,
3355    }
3356}
3357
3358pub mod testing {
3359    use crate::testing::*;
3360
3361    /// this function is not used, but is available for example and testing purposes
3362    pub fn start_https_worker(
3363        config: HttpsListenerConfig,
3364        channel: ProxyChannel,
3365        max_buffers: usize,
3366        buffer_size: usize,
3367    ) -> anyhow::Result<()> {
3368        let address = config.address.into();
3369
3370        let ServerParts {
3371            event_loop,
3372            registry,
3373            sessions,
3374            pool,
3375            backends,
3376            client_scm_socket: _,
3377            server_scm_socket,
3378            server_config,
3379        } = prebuild_server(max_buffers, buffer_size, true)?;
3380
3381        let token = {
3382            let mut sessions = sessions.borrow_mut();
3383            let entry = sessions.slab.vacant_entry();
3384            let key = entry.key();
3385            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3386                protocol: Protocol::HTTPSListen,
3387            })));
3388            Token(key)
3389        };
3390
3391        let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3392        proxy
3393            .add_listener(config, token)
3394            .with_context(|| "Failed at creating adding the listener")?;
3395        proxy
3396            .activate_listener(&address, None)
3397            .with_context(|| "Failed at creating activating the listener")?;
3398
3399        let mut server = Server::new(
3400            event_loop,
3401            channel,
3402            server_scm_socket,
3403            sessions,
3404            pool,
3405            backends,
3406            None,
3407            Some(proxy),
3408            None,
3409            server_config,
3410            None,
3411            false,
3412        )
3413        .with_context(|| "Failed at creating server")?;
3414
3415        debug!("{} starting event loop", log_module_context!());
3416        server.run();
3417        debug!("{} ending event loop", log_module_context!());
3418        Ok(())
3419    }
3420}
3421
3422#[cfg(test)]
3423mod tests {
3424    use std::sync::Arc;
3425
3426    use sozu_command::{
3427        config::ListenerBuilder,
3428        proto::command::{CertificateAndKey, SocketAddress},
3429    };
3430
3431    use super::*;
3432    use crate::{
3433        router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode},
3434        socket::SocketHandler,
3435    };
3436
3437    /// A handshake whose last `recv` proved the socket empty hands over no
3438    /// READABLE: the upgraded mux must not `recv` for an EAGAIN (#1609).
3439    /// READABLE survives when the handshake still had it, or when rustls
3440    /// holds decrypted input; WRITABLE always does.
3441    #[test]
3442    fn the_upgraded_frontend_reads_only_when_a_read_is_due() {
3443        assert_eq!(
3444            upgraded_frontend_events(Ready::EMPTY, false),
3445            Ready::WRITABLE,
3446            "a drained socket and an empty rustls need no read"
3447        );
3448        assert_eq!(
3449            upgraded_frontend_events(Ready::WRITABLE, false),
3450            Ready::WRITABLE
3451        );
3452        assert_eq!(
3453            upgraded_frontend_events(Ready::READABLE, false),
3454            Ready::READABLE | Ready::WRITABLE,
3455            "an edge not yet read must survive the upgrade"
3456        );
3457        assert_eq!(
3458            upgraded_frontend_events(Ready::EMPTY, true),
3459            Ready::READABLE | Ready::WRITABLE,
3460            "plaintext rustls already holds raises no edge"
3461        );
3462        assert_eq!(
3463            upgraded_frontend_events(Ready::READABLE | Ready::HUP, true),
3464            Ready::READABLE | Ready::WRITABLE | Ready::HUP,
3465            "a half-close seen during the handshake must reach the mux"
3466        );
3467        assert_eq!(
3468            upgraded_frontend_events(Ready::HUP, false),
3469            Ready::WRITABLE | Ready::HUP,
3470            "HUP survives even when no read is due"
3471        );
3472        assert_eq!(
3473            upgraded_frontend_events(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED, true),
3474            Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::WRITE_CLOSED,
3475            "a hang-up seen during the handshake must reach the mux as one"
3476        );
3477    }
3478
3479    #[test]
3480    fn successful_tls_handshake_log_bounds_sni_and_alpn() {
3481        const SNI_SECRET: &str = "HTTPS_HANDSHAKE_SNI_SECRET_SENTINEL";
3482        const ALPN_SECRET: &str = "HTTPS_HANDSHAKE_ALPN_SECRET_SENTINEL";
3483
3484        let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
3485        let alpn = format!("{ALPN_SECRET}{}", "x".repeat(4096));
3486        let sni_len = sni.len();
3487        let alpn_len = alpn.len();
3488        let output = successful_tls_handshake_summary(Some(&sni), Some(&alpn));
3489
3490        for secret in [SNI_SECRET, ALPN_SECRET] {
3491            assert!(
3492                !output.contains(secret),
3493                "successful TLS handshake log leaked {secret}: {output}"
3494            );
3495        }
3496        for metadata in [
3497            format!("sni_bytes=Some({sni_len})"),
3498            format!("alpn_bytes=Some({alpn_len})"),
3499        ] {
3500            assert!(
3501                output.contains(&metadata),
3502                "successful TLS handshake log omitted {metadata}: {output}"
3503            );
3504        }
3505        assert!(
3506            output.len() <= 512,
3507            "successful TLS handshake log is not bounded: {} bytes",
3508            output.len()
3509        );
3510    }
3511
3512    fn proxy_with_certificate_domain(domain: String) -> HttpsProxy {
3513        let crate::testing::ServerParts {
3514            registry,
3515            sessions,
3516            pool,
3517            backends,
3518            ..
3519        } = crate::testing::prebuild_server(16, 16_384, false)
3520            .expect("test HTTPS proxy dependencies must initialize");
3521        let address = SocketAddress::new_v4(127, 0, 0, 1, crate::testing::provide_port());
3522        let mut config = ListenerBuilder::new_https(address)
3523            .to_tls(None)
3524            .expect("test HTTPS listener config must build");
3525        config.cipher_list = vec!["TLS13_AES_256_GCM_SHA384".to_owned()];
3526        config.groups_list = vec!["P-256".to_owned()];
3527        let mut proxy = HttpsProxy::new(registry, sessions, pool, backends);
3528        proxy
3529            .add_listener(config, Token(10))
3530            .expect("test HTTPS listener must be added");
3531        proxy
3532            .add_certificate(AddCertificate {
3533                address,
3534                certificate: CertificateAndKey {
3535                    certificate: include_str!("../assets/certificate.pem").to_owned(),
3536                    key: include_str!("../assets/key.pem").to_owned(),
3537                    certificate_chain: Vec::new(),
3538                    versions: Vec::new(),
3539                    names: vec![domain],
3540                },
3541                expired_at: None,
3542            })
3543            .expect("test certificate must be added");
3544        proxy
3545    }
3546
3547    /// The two `query_*` paths take the resolver mutex with `unwrap_msg!`,
3548    /// while every `add`/`remove`/`replace` sibling takes it with
3549    /// `.lock().map_err(|e| ProxyError::Lock(e.to_string()))?`
3550    /// (https.rs `add_certificate`/`remove_certificate`/`replace_certificate`).
3551    /// That asymmetry is the finding: a poisoned resolver — any earlier panic
3552    /// while the lock was held — turns a read-only `QueryCertificates`
3553    /// control-plane request into a worker kill instead of an error response.
3554    ///
3555    /// To SEE THIS RED: put `unwrap_msg!(owned.resolver.0.lock())` back in
3556    /// `query_all_certificates` and `query_certificate_for_domain` (restoring
3557    /// the `util::UnwrapLog` import the removal made unused) — both calls
3558    /// below then panic with `PoisonError` instead of returning
3559    /// `ProxyError::Lock`.
3560    #[test]
3561    fn a_poisoned_resolver_fails_the_certificate_queries_instead_of_the_worker() {
3562        let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3563
3564        let resolver = proxy
3565            .listeners
3566            .values()
3567            .next()
3568            .expect("the test proxy has one listener")
3569            .borrow()
3570            .resolver()
3571            .clone();
3572
3573        // Poison the mutex exactly as a panic inside the worker would: hold
3574        // the guard, unwind. `join` swallows the unwind so the test process
3575        // survives to observe the poisoned state.
3576        let poisoner = resolver.clone();
3577        let unwound = std::thread::spawn(move || {
3578            let _guard = poisoner.0.lock().expect("first lock is not poisoned");
3579            panic!("simulated worker panic while holding the resolver lock");
3580        })
3581        .join();
3582        assert!(unwound.is_err(), "the poisoning thread must have unwound");
3583        assert!(
3584            resolver.0.lock().is_err(),
3585            "the resolver mutex must now be poisoned"
3586        );
3587
3588        assert!(
3589            matches!(proxy.query_all_certificates(), Err(ProxyError::Lock(_))),
3590            "Certificates::All on a poisoned resolver must report a lock error"
3591        );
3592        assert!(
3593            matches!(
3594                proxy.query_certificate_for_domain("lolcatho.st".to_owned()),
3595                Err(ProxyError::Lock(_))
3596            ),
3597            "Certificates::Domain on a poisoned resolver must report a lock error"
3598        );
3599    }
3600
3601    /// `String::from_utf8(trie_key).unwrap()` in `query_all_certificates` is a
3602    /// second unconditional panic on a value the function does not own. The
3603    /// control plane cannot currently reach it — `AddCertificate.names` is
3604    /// `Vec<String>` and `tls.rs::add_certificate` inserts
3605    /// `String::into_bytes()`, while `TrieNode::to_hashmap` clones the stored
3606    /// key back verbatim — so this is an invariant, and the red below is BY
3607    /// MUTATION of the trie: `TrieNode<Fingerprint>` is `pub` and its key type
3608    /// is `Vec<u8>`, so the type permits what the control plane does not. A
3609    /// certificate listing must not be able to kill the worker over a key it
3610    /// merely echoes.
3611    ///
3612    /// To SEE THIS RED: put `String::from_utf8(k).unwrap()` back in
3613    /// `query_all_certificates` — this test then panics with
3614    /// `FromUtf8Error`.
3615    #[test]
3616    fn a_non_utf8_resolver_key_is_listed_lossily_not_panicked() {
3617        let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3618
3619        let resolver = proxy
3620            .listeners
3621            .values()
3622            .next()
3623            .expect("the test proxy has one listener")
3624            .borrow()
3625            .resolver()
3626            .clone();
3627        {
3628            let mut owned = resolver.0.lock().expect("resolver lock");
3629            let inserted = owned
3630                .domains
3631                .insert(vec![0xff, 0xfe], Fingerprint(vec![0x01, 0x02]));
3632            assert_ne!(
3633                inserted,
3634                crate::router::pattern_trie::InsertResult::Failed,
3635                "the SNI trie must accept the non-UTF-8 key this test relies on"
3636            );
3637        }
3638
3639        let response = proxy
3640            .query_all_certificates()
3641            .expect("a non-UTF-8 trie key must not fail the query");
3642        assert!(
3643            response.is_some(),
3644            "Certificates::All must still answer with a listing"
3645        );
3646    }
3647
3648    #[test]
3649    fn query_all_certificates_log_redacts_response_domains() {
3650        const DOMAIN_SECRET: &str = "QUERY_ALL_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3651
3652        // Below `router::MAX_HOSTNAME_LENGTH` so the certificate passes
3653        // add-time name validation; the redaction property under test is
3654        // length-independent.
3655        let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3656        let output = crate::capture_test_logs(move || {
3657            let mut proxy = proxy_with_certificate_domain(domain);
3658            proxy
3659                .query_all_certificates()
3660                .expect("certificate query must succeed");
3661        });
3662
3663        assert!(
3664            !output.contains(DOMAIN_SECRET),
3665            "Certificates::All log leaked response domain {DOMAIN_SECRET}"
3666        );
3667        for metadata in ["listeners_count=1", "certificates_count=1"] {
3668            assert!(
3669                output.contains(metadata),
3670                "Certificates::All log omitted bounded metadata {metadata}: {output}"
3671            );
3672        }
3673        assert!(
3674            output.len() <= 1024,
3675            "Certificates::All log capture is not bounded: {} bytes",
3676            output.len()
3677        );
3678    }
3679
3680    #[test]
3681    fn query_certificate_for_domain_log_redacts_request_and_response_domain() {
3682        const DOMAIN_SECRET: &str = "QUERY_ONE_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3683
3684        // Below `router::MAX_HOSTNAME_LENGTH` -- see
3685        // `query_all_certificates_log_redacts_response_domains`.
3686        let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3687        let domain_len = domain.len();
3688        let output = crate::capture_test_logs_at_level("debug", move || {
3689            let mut proxy = proxy_with_certificate_domain(domain.clone());
3690            let response = proxy.notify(WorkerRequest {
3691                id: "query-domain-redaction-test".to_owned(),
3692                content: RequestType::QueryCertificatesFromWorkers(
3693                    sozu_command::proto::command::QueryCertificatesFilters {
3694                        domain: Some(domain),
3695                        fingerprint: None,
3696                    },
3697                )
3698                .into(),
3699            });
3700            assert_eq!(
3701                response.status,
3702                sozu_command::proto::command::ResponseStatus::Ok as i32,
3703                "domain certificate query must succeed: {}",
3704                response.message
3705            );
3706        });
3707
3708        assert!(
3709            !output.contains(DOMAIN_SECRET),
3710            "Certificates::Domain log leaked request or response domain {DOMAIN_SECRET}"
3711        );
3712        for metadata in [
3713            format!("domain_bytes={domain_len}"),
3714            "listeners_count=1".to_owned(),
3715            "certificates_count=1".to_owned(),
3716        ] {
3717            assert!(
3718                output.contains(&metadata),
3719                "Certificates::Domain log omitted bounded metadata {metadata}: {output}"
3720            );
3721        }
3722        assert!(
3723            output.len() <= 1024,
3724            "Certificates::Domain log capture is not bounded: {} bytes",
3725            output.len()
3726        );
3727    }
3728
3729    #[test]
3730    fn frontend_from_request_test() {
3731        let cluster_id1 = ClusterId::from("cluster_1");
3732        let cluster_id2 = ClusterId::from("cluster_2");
3733        let cluster_id3 = ClusterId::from("cluster_3");
3734        let uri1 = "/".to_owned();
3735        let uri2 = "/yolo".to_owned();
3736        let uri3 = "/yolo/swag".to_owned();
3737
3738        let mut fronts = Router::new();
3739        assert!(fronts.add_tree_rule(
3740            "lolcatho.st".as_bytes(),
3741            &PathRule::Prefix(uri1),
3742            &MethodRule::new(None),
3743            &Route::ClusterId(cluster_id1.clone())
3744        ));
3745        assert!(fronts.add_tree_rule(
3746            "lolcatho.st".as_bytes(),
3747            &PathRule::Prefix(uri2),
3748            &MethodRule::new(None),
3749            &Route::ClusterId(cluster_id2)
3750        ));
3751        assert!(fronts.add_tree_rule(
3752            "lolcatho.st".as_bytes(),
3753            &PathRule::Prefix(uri3),
3754            &MethodRule::new(None),
3755            &Route::ClusterId(cluster_id3)
3756        ));
3757        assert!(fronts.add_tree_rule(
3758            "other.domain".as_bytes(),
3759            &PathRule::Prefix("test".to_string()),
3760            &MethodRule::new(None),
3761            &Route::ClusterId(cluster_id1)
3762        ));
3763
3764        let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
3765        let resolver = Arc::new(MutexCertificateResolver::default());
3766
3767        let crypto_provider = Arc::new(default_provider());
3768
3769        let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
3770            .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
3771            .expect("could not create rustls config server")
3772            .with_no_client_auth()
3773            .with_cert_resolver(resolver.clone());
3774
3775        let rustls_details = Arc::new(server_config);
3776
3777        let default_config = ListenerBuilder::new_https(address)
3778            .to_tls(None)
3779            .expect("Could not create default HTTPS listener config");
3780
3781        println!("it doesn't even matter");
3782
3783        let listener = HttpsListener {
3784            parked_listener: None,
3785            nodelay_backlog: false,
3786            listener: None,
3787            address: address.into(),
3788            fronts,
3789            rustls_details,
3790            resolver,
3791            answers: Rc::new(RefCell::new(
3792                HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
3793            )),
3794            config: default_config,
3795            token: Token(0),
3796            active: true,
3797            tags: BTreeMap::new(),
3798        };
3799
3800        println!("TEST {}", line!());
3801        let frontend1 = listener.frontend_from_request(
3802            "lolcatho.st",
3803            "/",
3804            &Method::Get,
3805            &crate::router::NoHeaders,
3806        );
3807        assert_eq!(
3808            frontend1
3809                .expect("should find a frontend")
3810                .cluster_id
3811                .as_deref(),
3812            Some("cluster_1")
3813        );
3814        println!("TEST {}", line!());
3815        let frontend2 = listener.frontend_from_request(
3816            "lolcatho.st",
3817            "/test",
3818            &Method::Get,
3819            &crate::router::NoHeaders,
3820        );
3821        assert_eq!(
3822            frontend2
3823                .expect("should find a frontend")
3824                .cluster_id
3825                .as_deref(),
3826            Some("cluster_1")
3827        );
3828        println!("TEST {}", line!());
3829        let frontend3 = listener.frontend_from_request(
3830            "lolcatho.st",
3831            "/yolo/test",
3832            &Method::Get,
3833            &crate::router::NoHeaders,
3834        );
3835        assert_eq!(
3836            frontend3
3837                .expect("should find a frontend")
3838                .cluster_id
3839                .as_deref(),
3840            Some("cluster_2")
3841        );
3842        println!("TEST {}", line!());
3843        let frontend4 = listener.frontend_from_request(
3844            "lolcatho.st",
3845            "/yolo/swag",
3846            &Method::Get,
3847            &crate::router::NoHeaders,
3848        );
3849        assert_eq!(
3850            frontend4
3851                .expect("should find a frontend")
3852                .cluster_id
3853                .as_deref(),
3854            Some("cluster_3")
3855        );
3856        println!("TEST {}", line!());
3857        let frontend5 =
3858            listener.frontend_from_request("domain", "/", &Method::Get, &crate::router::NoHeaders);
3859        assert!(frontend5.is_err());
3860        // assert!(false);
3861    }
3862
3863    #[test]
3864    fn wildcard_certificate_names() {
3865        let mut trie = TrieNode::root();
3866
3867        trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
3868        trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
3869        trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
3870        trie.domain_insert(
3871            "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
3872            3u8,
3873        );
3874        trie.domain_insert(
3875            "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
3876            4u8,
3877        );
3878
3879        let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
3880        println!("query result: {res:?}");
3881
3882        assert_eq!(
3883            trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
3884            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3885        );
3886        assert_eq!(
3887            trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
3888            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3889        );
3890    }
3891
3892    #[test]
3893    fn wildcard_with_subdomains() {
3894        let mut trie = TrieNode::root();
3895
3896        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3897        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3898
3899        let res = trie.domain_lookup(b"sub.test.example.com", true);
3900        println!("query result: {res:?}");
3901
3902        assert_eq!(
3903            trie.domain_lookup(b"sub.test.example.com", true),
3904            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3905        );
3906        assert_eq!(
3907            trie.domain_lookup(b"hello.sub.test.example.com", true),
3908            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3909        );
3910
3911        // now try in a different order
3912        let mut trie = TrieNode::root();
3913
3914        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3915        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3916
3917        let res = trie.domain_lookup(b"sub.test.example.com", true);
3918        println!("query result: {res:?}");
3919
3920        assert_eq!(
3921            trie.domain_lookup(b"sub.test.example.com", true),
3922            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3923        );
3924        assert_eq!(
3925            trie.domain_lookup(b"hello.sub.test.example.com", true),
3926            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3927        );
3928    }
3929
3930    /// sozu#322: the HTTPS twin of `lib/src/http.rs`'s
3931    /// `a_listener_with_an_unknown_forwarded_headers_value_is_refused`.
3932    #[test]
3933    fn a_listener_with_an_unknown_forwarded_headers_value_is_refused() {
3934        let build = |forwarded_headers: Option<i32>| {
3935            let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1043))
3936                .to_tls(None)
3937                .expect("default HTTPS listener config");
3938            cfg.forwarded_headers = forwarded_headers;
3939            HttpsListener::try_new(cfg, Token(0))
3940        };
3941        assert!(build(Some(-1)).is_err(), "an unknown mode must be refused");
3942        let listener = build(Some(ForwardedHeaders::None as i32)).expect("a known mode");
3943        assert_eq!(listener.get_forwarded_headers(), ForwardedHeaders::None);
3944    }
3945
3946    /// The HTTPS twin of `lib/src/http.rs`'s
3947    /// `get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold`
3948    /// (sozu-proxy/sozu#1418): the same thirteen positional arguments are
3949    /// spelled out a second time here, so the two copies need the same
3950    /// transposition guard. Every value is distinct.
3951    #[test]
3952    fn get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold() {
3953        let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1042))
3954            .to_tls(None)
3955            .expect("default HTTPS listener config");
3956        cfg.h2_max_rst_stream_per_window = Some(101);
3957        cfg.h2_max_ping_per_window = Some(102);
3958        cfg.h2_max_settings_per_window = Some(103);
3959        cfg.h2_max_empty_data_per_window = Some(104);
3960        cfg.h2_max_window_update_stream0_per_window = Some(105);
3961        cfg.h2_max_continuation_frames = Some(106);
3962        cfg.h2_max_glitch_count = Some(107);
3963        cfg.h2_max_rst_stream_lifetime = Some(108);
3964        cfg.h2_max_rst_stream_abusive_lifetime = Some(109);
3965        cfg.h2_max_rst_stream_emitted_lifetime = Some(110);
3966        cfg.h2_max_header_list_size = Some(111);
3967        cfg.h2_max_header_table_size = Some(112);
3968        cfg.h2_max_header_fields = Some(113);
3969        cfg.h2_stream_refusal_percent = Some(114);
3970
3971        let listener = HttpsListener::try_new(cfg, Token(0)).expect("build listener");
3972        let flood = listener.get_h2_flood_config();
3973
3974        assert_eq!(flood.max_rst_stream_per_window(), 101);
3975        assert_eq!(flood.max_ping_per_window(), 102);
3976        assert_eq!(flood.max_settings_per_window(), 103);
3977        assert_eq!(flood.max_empty_data_per_window(), 104);
3978        assert_eq!(flood.max_window_update_stream0_per_window(), 105);
3979        assert_eq!(flood.max_continuation_frames(), 106);
3980        assert_eq!(flood.max_glitch_count(), 107);
3981        assert_eq!(flood.max_rst_stream_lifetime(), 108);
3982        assert_eq!(flood.max_rst_stream_abusive_lifetime(), 109);
3983        assert_eq!(flood.max_rst_stream_emitted_lifetime(), 110);
3984        assert_eq!(flood.max_header_list_size(), 111);
3985        assert_eq!(flood.max_header_table_size(), 112);
3986        assert_eq!(flood.max_header_fields(), 113);
3987        assert_eq!(flood.stream_refusal_percent(), 114);
3988    }
3989
3990    #[test]
3991    fn h2_stream_idle_timeout_inherits_back_timeout() {
3992        use std::time::Duration;
3993
3994        let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
3995        let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
3996            let mut cfg = ListenerBuilder::new_https(address)
3997                .to_tls(None)
3998                .expect("default HTTPS listener config");
3999            cfg.back_timeout = back_timeout;
4000            cfg.h2_stream_idle_timeout_seconds = explicit;
4001            HttpsListener::try_new(cfg, Token(0)).expect("build listener")
4002        };
4003
4004        // Knob unset: inherit back_timeout when it exceeds the 30s floor.
4005        assert_eq!(
4006            build(180, None).get_h2_stream_idle_timeout(),
4007            Duration::from_secs(180)
4008        );
4009
4010        // Knob unset, back_timeout below floor: stay at 30s.
4011        assert_eq!(
4012            build(5, None).get_h2_stream_idle_timeout(),
4013            Duration::from_secs(30)
4014        );
4015
4016        // Explicit values win in both directions.
4017        assert_eq!(
4018            build(180, Some(10)).get_h2_stream_idle_timeout(),
4019            Duration::from_secs(10)
4020        );
4021        assert_eq!(
4022            build(5, Some(600)).get_h2_stream_idle_timeout(),
4023            Duration::from_secs(600)
4024        );
4025
4026        // `Some(0)` is clamped to 1s.
4027        assert_eq!(
4028            build(180, Some(0)).get_h2_stream_idle_timeout(),
4029            Duration::from_secs(1)
4030        );
4031    }
4032
4033    // ── FrontRustls peer snapshot (the PROXY-protocol seeding decision) ──
4034
4035    /// Source address a PROXY v2 header advertises. Non-loopback so it can
4036    /// never collide with the address of the connection the test really opens.
4037    const PROXY_ADVERTISED_PEER: &str = "10.0.0.42:12345";
4038
4039    /// The peer snapshot handed to [`FrontRustls`] is the session's known peer
4040    /// address — on an expect-proxy frontend, the source the PROXY header
4041    /// advertised — and NOT `getpeername(2)` on the accepted socket, which on
4042    /// such a frontend is the load balancer.
4043    ///
4044    /// This is the one substitution a future editor is most likely to make
4045    /// here. Hoisting `let raw_peer = handshake.stream.peer_addr().ok();` above
4046    /// the struct literal and seeding from that compiles, passes every other
4047    /// test in this file, and silently restores the split this change exists to
4048    /// remove: the `HTTPS` line naming the client while the `MUX-*` lines name
4049    /// the load balancer, for one and the same request id.
4050    ///
4051    /// The test drives the real `upgrade_handshake`, so it pins the seeding
4052    /// site rather than the accessor. `session.peer_address` is set the way
4053    /// `upgrade_expect` sets it (https.rs, `self.peer_address =
4054    /// Some(session_address)` on the PROXY-header branch) instead of feeding a
4055    /// real PROXY header — that adoption already has its own `debug_assert_eq!`
4056    /// in `upgrade_expect` and its own e2e coverage; what is unguarded, and
4057    /// what this test guards, is the hand-off from there into the socket
4058    /// handler. `handshake.peer_address` is set to the same value because
4059    /// `upgrade_expect` passes `self.peer_address` straight into
4060    /// `TlsHandshake::new`: in production the two are always equal, so the test
4061    /// must not be able to pass or fail on the difference between them.
4062    ///
4063    /// To SEE THIS RED: in `upgrade_handshake`, put
4064    /// `let raw_peer = handshake.stream.peer_addr().ok();` before the
4065    /// `FrontRustls` literal and replace `configured_peer: self.peer_address,`
4066    /// with `configured_peer: raw_peer,`. (Reading `handshake.stream` inside
4067    /// the literal itself does not compile — the `stream:` field has already
4068    /// moved it — which is the only reason the mutation needs the hoist.)
4069    #[test]
4070    fn front_rustls_peer_snapshot_is_the_session_peer_not_the_accepted_socket() {
4071        let advertised: StdSocketAddr = PROXY_ADVERTISED_PEER
4072            .parse()
4073            .expect("the advertised peer literal must parse");
4074
4075        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4076        let listener = proxy
4077            .listeners
4078            .values()
4079            .next()
4080            .expect("the test proxy has one listener")
4081            .clone();
4082        let pool = Rc::downgrade(&proxy.pool);
4083        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4084            .expect("a test rustls server session must be created");
4085        let public_address = *listener.borrow().get_addr();
4086        let proxy = Rc::new(RefCell::new(proxy));
4087
4088        // A genuinely established loopback connection stands in for the one the
4089        // load balancer opened. `getpeername(2)` on it answers the balancer's
4090        // address and keeps answering it — the point being that the snapshot
4091        // must disagree with a live lookup that is perfectly healthy.
4092        let balancer = std::net::TcpListener::bind("127.0.0.1:0")
4093            .expect("test listener must bind to a loopback port");
4094        let balancer_peer = balancer
4095            .local_addr()
4096            .expect("test listener must report its local address");
4097        let stream =
4098            std::net::TcpStream::connect(balancer_peer).expect("loopback connect must complete");
4099        stream
4100            .set_nonblocking(true)
4101            .expect("mio requires a nonblocking stream");
4102        let stream = MioTcpStream::from_std(stream);
4103
4104        let mut session = HttpsSession::new(
4105            Duration::from_secs(30),
4106            Duration::from_secs(30),
4107            Duration::from_secs(30),
4108            Duration::from_secs(30),
4109            // Built on the direct route so the session owns exactly one stream
4110            // and one rustls session; the PROXY adoption is then applied below.
4111            false,
4112            listener,
4113            pool,
4114            proxy,
4115            public_address,
4116            rustls_details,
4117            stream,
4118            // What `accept(2)` reports for this connection: the far end, which
4119            // for the stand-in is the balancer.
4120            balancer_peer,
4121            Token(0),
4122            Duration::from_secs(0),
4123        );
4124
4125        assert_eq!(
4126            session.peer_address,
4127            Some(balancer_peer),
4128            "a direct-route session starts out with the accepted socket's peer"
4129        );
4130
4131        let mut handshake = match session.state.take() {
4132            HttpsStateMachine::Handshake(handshake) => handshake,
4133            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4134        };
4135
4136        // What `upgrade_expect` does once the PROXY header parses.
4137        session.peer_address = Some(advertised);
4138        handshake.peer_address = Some(advertised);
4139        assert_ne!(
4140            advertised, balancer_peer,
4141            "the advertised and connection peers must differ for this test to discriminate"
4142        );
4143
4144        let upgraded = session
4145            .upgrade_handshake(handshake)
4146            .expect("the handshake must upgrade to a mux session");
4147        // Borrowed rather than moved out: `ConnectionH2` implements `Drop`, so
4148        // its `socket` field cannot leave the connection.
4149        let (snapshot, live_lookup) = match &upgraded {
4150            HttpsStateMachine::Mux(mux) => match &mux.frontend {
4151                mux::Connection::H1(connection) => (
4152                    connection.socket.peer_addr(),
4153                    connection.socket.socket_ref().peer_addr().ok(),
4154                ),
4155                mux::Connection::H2(connection) => (
4156                    connection.socket.peer_addr(),
4157                    connection.socket.socket_ref().peer_addr().ok(),
4158                ),
4159            },
4160            _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4161        };
4162
4163        // Premise: the live lookup is healthy and still reports the balancer,
4164        // so the assertion below cannot pass for the wrong reason.
4165        assert_eq!(
4166            live_lookup,
4167            Some(balancer_peer),
4168            "the frontend socket must still be connected to the stand-in balancer"
4169        );
4170        assert_eq!(
4171            snapshot,
4172            Some(advertised),
4173            "FrontRustls must carry the PROXY-advertised source, not the accepted socket's peer"
4174        );
4175    }
4176
4177    /// The `or_else` arm of [`FrontRustls::peer_addr`] answers for a handler
4178    /// built with no peer address. No production route builds one any more:
4179    /// the direct (non-expect-proxy) route seeds `peer_address` with the
4180    /// address `accept(2)` returned (sozu-proxy/sozu#1586), and the
4181    /// expect-proxy route only reaches the handshake with the PROXY-advertised
4182    /// source. The arm stays as defence in depth — without it a `None` would
4183    /// blank the `peer=` slot for the whole connection even though the socket
4184    /// is healthy and the kernel would answer — so the test builds that
4185    /// handler by hand.
4186    ///
4187    /// `SessionTcpStream`'s identical arm has had
4188    /// `socket::tests::session_tcp_stream_peer_addr_falls_back_to_the_live_lookup`
4189    /// since it was written; this is the missing twin for `FrontRustls`.
4190    ///
4191    /// To SEE THIS RED: in `impl SocketHandler for FrontRustls`, drop the
4192    /// `.or_else(|| self.stream.peer_addr().ok())` from `peer_addr`. The
4193    /// handler then answers `None` for a perfectly healthy connection.
4194    #[test]
4195    fn front_rustls_peer_addr_falls_back_to_the_live_lookup() {
4196        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4197        let listener = proxy
4198            .listeners
4199            .values()
4200            .next()
4201            .expect("the test proxy has one listener")
4202            .clone();
4203        let pool = Rc::downgrade(&proxy.pool);
4204        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4205            .expect("a test rustls server session must be created");
4206        let public_address = *listener.borrow().get_addr();
4207        let proxy = Rc::new(RefCell::new(proxy));
4208
4209        let peer = std::net::TcpListener::bind("127.0.0.1:0")
4210            .expect("test listener must bind to a loopback port");
4211        let live_peer = peer
4212            .local_addr()
4213            .expect("test listener must report its local address");
4214        let stream =
4215            std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
4216        stream
4217            .set_nonblocking(true)
4218            .expect("mio requires a nonblocking stream");
4219        let stream = MioTcpStream::from_std(stream);
4220
4221        let mut session = HttpsSession::new(
4222            Duration::from_secs(30),
4223            Duration::from_secs(30),
4224            Duration::from_secs(30),
4225            Duration::from_secs(30),
4226            false,
4227            listener,
4228            pool,
4229            proxy,
4230            public_address,
4231            rustls_details,
4232            stream,
4233            live_peer,
4234            Token(0),
4235            Duration::from_secs(0),
4236        );
4237
4238        let mut handshake = match session.state.take() {
4239            HttpsStateMachine::Handshake(handshake) => handshake,
4240            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4241        };
4242
4243        // A session that knows no peer, which the handler must fall back from.
4244        session.peer_address = None;
4245        handshake.peer_address = None;
4246
4247        let upgraded = session
4248            .upgrade_handshake(handshake)
4249            .expect("the handshake must upgrade to a mux session");
4250        let snapshot = match &upgraded {
4251            HttpsStateMachine::Mux(mux) => match &mux.frontend {
4252                mux::Connection::H1(connection) => connection.socket.peer_addr(),
4253                mux::Connection::H2(connection) => connection.socket.peer_addr(),
4254            },
4255            _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4256        };
4257
4258        assert_eq!(
4259            snapshot,
4260            Some(live_peer),
4261            "with nothing cached, FrontRustls::peer_addr must degrade to getpeername(2)"
4262        );
4263    }
4264
4265    /// `sozu query certificates --domain <d>` must ask the resolver the
4266    /// same question a TLS handshake asks it.
4267    ///
4268    /// The resolver keys its SNI trie on ASCII-lowercased certificate
4269    /// names (`CertifiedKeyWrapper::try_from`, `lib/src/tls.rs`), because
4270    /// rustls hands `ResolvesServerCert::resolve` an SNI it has already
4271    /// lowercased and no other key can ever be looked up at handshake
4272    /// time. The operator query has to be normalised the same way, or
4273    /// `sozu query certificates --domain MiXeD.Example.COM` reports the
4274    /// certificate missing while the proxy is serving it on every
4275    /// handshake for that name.
4276    ///
4277    /// To SEE THIS RED: in `query_certificate_for_domain`, look the
4278    /// domain up with `domain.as_bytes()` instead of
4279    /// `domain_key.as_bytes()`. The two case-varying queries return zero
4280    /// summaries.
4281    #[test]
4282    fn query_certificate_for_domain_answers_any_spelling_of_the_name() {
4283        let mut proxy = proxy_with_certificate_domain("MiXeD.Example.COM".to_owned());
4284
4285        let mut summaries_for = |query: &str| {
4286            let response = proxy
4287                .query_certificate_for_domain(query.to_owned())
4288                .expect("the domain certificate query must succeed")
4289                .expect("the domain certificate query must return content");
4290            match response.content_type {
4291                Some(ContentType::CertificatesByAddress(list)) => list
4292                    .certificates
4293                    .iter()
4294                    .map(|entry| entry.certificate_summaries.len())
4295                    .sum::<usize>(),
4296                _ => panic!("the domain query must answer with CertificatesByAddress"),
4297            }
4298        };
4299
4300        for query in [
4301            "MiXeD.Example.COM",
4302            "mixed.example.com",
4303            "MIXED.EXAMPLE.COM",
4304        ] {
4305            assert_eq!(
4306                summaries_for(query),
4307                1,
4308                "querying {query:?} must find the certificate stored as MiXeD.Example.COM",
4309            );
4310        }
4311
4312        assert_eq!(
4313            summaries_for("other.example.com"),
4314            0,
4315            "normalising the query must not make an unrelated domain match",
4316        );
4317    }
4318
4319    /// An HTTPS session upgraded to the mux over a live loopback connection,
4320    /// with the accepted peer standing in for the client. The TLS handshake
4321    /// never ran; nothing here reads what the client receives beyond the FIN.
4322    fn https_mux_session_with_peer() -> (HttpsSession, std::net::TcpStream) {
4323        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4324        let listener = proxy
4325            .listeners
4326            .values()
4327            .next()
4328            .expect("the test proxy has one listener")
4329            .clone();
4330        let pool = Rc::downgrade(&proxy.pool);
4331        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4332            .expect("a test rustls server session must be created");
4333        let public_address = *listener.borrow().get_addr();
4334        let proxy = Rc::new(RefCell::new(proxy));
4335
4336        let accepting = std::net::TcpListener::bind("127.0.0.1:0")
4337            .expect("test listener must bind to a loopback port");
4338        let address = accepting
4339            .local_addr()
4340            .expect("test listener must report its local address");
4341        let stream = std::net::TcpStream::connect(address).expect("loopback connect must complete");
4342        let (client, client_address) = accepting.accept().expect("loopback accept must complete");
4343        client
4344            .set_nonblocking(true)
4345            .expect("the client stand-in must be nonblocking");
4346        stream
4347            .set_nonblocking(true)
4348            .expect("mio requires a nonblocking stream");
4349
4350        let mut session = HttpsSession::new(
4351            Duration::from_secs(30),
4352            Duration::from_secs(30),
4353            Duration::from_secs(30),
4354            Duration::from_secs(30),
4355            false,
4356            listener,
4357            pool,
4358            proxy,
4359            public_address,
4360            rustls_details,
4361            MioTcpStream::from_std(stream),
4362            client_address,
4363            Token(0),
4364            Duration::from_secs(0),
4365        );
4366        let handshake = match session.state.take() {
4367            HttpsStateMachine::Handshake(handshake) => handshake,
4368            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4369        };
4370        session.state = session
4371            .upgrade_handshake(handshake)
4372            .expect("the handshake must upgrade to a mux session");
4373        (session, client)
4374    }
4375
4376    /// Close `session`, drop it as the event loop does, and return the
4377    /// `shutdown(SHUT_WR)` calls the close issued and whether `client` then
4378    /// observed the FIN.
4379    fn frontend_shutdowns_of_a_close(
4380        mut session: HttpsSession,
4381        client: &mut std::net::TcpStream,
4382    ) -> (usize, bool) {
4383        use std::io::Read;
4384
4385        let before = mux::shutdown_writes();
4386        session.close();
4387        let shutdowns = mux::shutdown_writes() - before;
4388        drop(session);
4389
4390        let deadline = Instant::now() + Duration::from_secs(2);
4391        let mut chunk = [0u8; 4096];
4392        let saw_fin = loop {
4393            match client.read(&mut chunk) {
4394                Ok(0) => break true,
4395                Ok(_) => {}
4396                Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
4397                    std::thread::sleep(Duration::from_millis(5));
4398                }
4399                Err(_) => break false,
4400            }
4401        };
4402        (shutdowns, saw_fin)
4403    }
4404
4405    /// Issue #1603: once the client has closed its side, `HttpsSession::close`
4406    /// does not `shutdown(SHUT_WR)` the frontend socket — on an H2 client that
4407    /// had already reset, that call failed with `ENOTCONN` on every
4408    /// connection. The client, which only half-closed here and is still
4409    /// reading, gets its FIN from the close alone.
4410    ///
4411    /// TO SEE THIS RED: make `mux::shutdown_write` ignore `peer_closed`, or
4412    /// drop the `peer_disconnected` arm from `HttpsSession::close` (then only
4413    /// the `read EOF` case fails). The first assertion fails with `left: 1`,
4414    /// `right: 0`.
4415    ///
4416    /// Two ways to know it: the HUP the event loop records for the client's
4417    /// FIN, and `FrontRustls::peer_disconnected`, set by the read that met the
4418    /// EOF or the `close_notify` — the H2 case, where the close follows in
4419    /// the same pass and no epoll event reports it.
4420    #[test]
4421    fn https_close_skips_the_frontend_shutdown_once_the_client_closed() {
4422        for how in ["epoll HUP", "read EOF"] {
4423            let (mut session, mut client) = https_mux_session_with_peer();
4424            client
4425                .shutdown(std::net::Shutdown::Write)
4426                .expect("the client stand-in must send its FIN");
4427            let HttpsStateMachine::Mux(mux) = &mut session.state else {
4428                unreachable!("https_mux_session_with_peer returns a mux session")
4429            };
4430            if how == "epoll HUP" {
4431                mux.frontend.readiness_mut().event.insert(Ready::HUP);
4432            } else {
4433                match &mut mux.frontend {
4434                    mux::Connection::H1(connection) => connection.socket.peer_disconnected = true,
4435                    mux::Connection::H2(connection) => connection.socket.peer_disconnected = true,
4436                }
4437            }
4438
4439            let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4440            assert_eq!(
4441                shutdowns, 0,
4442                "a frontend whose client closed ({how}) must not be shut down before its close"
4443            );
4444            assert!(
4445                saw_fin,
4446                "the close alone must still deliver the FIN to a half-closed client ({how})"
4447            );
4448        }
4449    }
4450
4451    /// The other half of #1603: a client that is still there keeps the
4452    /// frontend `shutdown(SHUT_WR)` and observes the FIN.
4453    #[test]
4454    fn https_close_keeps_the_frontend_shutdown_while_the_client_is_there() {
4455        let (session, mut client) = https_mux_session_with_peer();
4456        let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4457        assert_eq!(
4458            shutdowns, 1,
4459            "a frontend whose client is still there must keep its shutdown(SHUT_WR)"
4460        );
4461        assert!(saw_fin, "the client must observe the FIN");
4462    }
4463}
4464
4465#[cfg(test)]
4466mod listener_sibling_tests {
4467    use sozu_command::{
4468        config::ListenerBuilder,
4469        proto::command::{
4470            CertificateAndKey, HstsConfig, PathRule, RequestHttpFrontend, SocketAddress,
4471        },
4472    };
4473
4474    use super::*;
4475    use crate::testing::{ServerParts, prebuild_server, provide_port};
4476
4477    fn proxy() -> HttpsProxy {
4478        let ServerParts {
4479            registry,
4480            sessions,
4481            pool,
4482            backends,
4483            ..
4484        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4485        HttpsProxy::new(registry, sessions, pool, backends)
4486    }
4487
4488    fn add(proxy: &mut HttpsProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
4489        let config = ListenerBuilder::new_https(address)
4490            .with_interface(interface)
4491            .to_tls(None)
4492            .expect("could not build the listener config");
4493        proxy
4494            .add_listener(config, Token(token))
4495            .expect("could not add the listener");
4496    }
4497
4498    fn certificate(address: SocketAddress) -> AddCertificate {
4499        AddCertificate {
4500            address,
4501            certificate: CertificateAndKey {
4502                certificate: include_str!("../assets/certificate.pem").to_owned(),
4503                key: include_str!("../assets/key.pem").to_owned(),
4504                certificate_chain: Vec::new(),
4505                versions: Vec::new(),
4506                names: vec!["example.com".to_owned()],
4507            },
4508            expired_at: None,
4509        }
4510    }
4511
4512    fn front(address: SocketAddress) -> RequestHttpFrontend {
4513        RequestHttpFrontend {
4514            cluster_id: Some("cluster-a".to_owned()),
4515            address,
4516            hostname: "example.com".to_owned(),
4517            path: PathRule::prefix("/".to_owned()),
4518            ..Default::default()
4519        }
4520    }
4521
4522    fn serves_certificate(proxy: &HttpsProxy, token: usize) -> bool {
4523        proxy.listeners[&Token(token)]
4524            .borrow()
4525            .resolver
4526            .0
4527            .lock()
4528            .expect("resolver lock")
4529            .domains
4530            .domain_lookup(b"example.com", true)
4531            .is_some()
4532    }
4533
4534    fn routes(proxy: &HttpsProxy, token: usize) -> bool {
4535        proxy.listeners[&Token(token)]
4536            .borrow()
4537            .fronts
4538            .lookup("example.com", "/", &Method::Get)
4539            .is_ok()
4540    }
4541
4542    /// An HTTPS listener added on an address that already serves a certificate
4543    /// and a frontend takes both over from the listener already there, so it
4544    /// completes TLS handshakes and routes from its first connection
4545    /// (sozu-proxy/sozu#719). It gets its own copy of the certificates: a
4546    /// later change reaches it through the fan-out, like its sibling.
4547    #[test]
4548    fn a_listener_added_next_to_a_sibling_takes_over_its_certificates_and_frontends() {
4549        let mut proxy = proxy();
4550        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4551        add(&mut proxy, address, None, 0);
4552        proxy
4553            .add_certificate(certificate(address))
4554            .expect("add the certificate");
4555        proxy
4556            .add_https_frontend(front(address))
4557            .expect("add the frontend");
4558        add(&mut proxy, address, Some("lo"), 1);
4559
4560        assert!(serves_certificate(&proxy, 0));
4561        assert!(
4562            serves_certificate(&proxy, 1),
4563            "the listener added after the certificate must serve it too"
4564        );
4565        assert!(
4566            routes(&proxy, 1),
4567            "the listener added after the frontend must route it too"
4568        );
4569
4570        proxy
4571            .remove_https_frontend(front(address))
4572            .expect("remove the frontend");
4573        assert!(!routes(&proxy, 0));
4574        assert!(!routes(&proxy, 1));
4575    }
4576    fn hsts(max_age: u32) -> HstsConfig {
4577        HstsConfig {
4578            enabled: Some(true),
4579            max_age: Some(max_age),
4580            ..Default::default()
4581        }
4582    }
4583
4584    fn add_with_hsts(
4585        proxy: &mut HttpsProxy,
4586        address: SocketAddress,
4587        interface: Option<&str>,
4588        token: usize,
4589        hsts: Option<HstsConfig>,
4590    ) {
4591        let mut config = ListenerBuilder::new_https(address)
4592            .with_interface(interface)
4593            .to_tls(None)
4594            .expect("could not build the listener config");
4595        config.hsts = hsts;
4596        proxy
4597            .add_listener(config, Token(token))
4598            .expect("could not add the listener");
4599    }
4600
4601    /// A frontend carrying policy (here, tags) so the router stores it as a
4602    /// `Route::Frontend` rather than the lightweight `Route::ClusterId`.
4603    fn tagged_front(address: SocketAddress) -> RequestHttpFrontend {
4604        RequestHttpFrontend {
4605            tags: BTreeMap::from([("owner".to_owned(), "team-a".to_owned())]),
4606            ..front(address)
4607        }
4608    }
4609
4610    /// The `Strict-Transport-Security` value the listener emits for
4611    /// `example.com/`, if any.
4612    fn served_hsts(proxy: &HttpsProxy, token: usize) -> Option<String> {
4613        let route = proxy.listeners[&Token(token)]
4614            .borrow()
4615            .fronts
4616            .lookup("example.com", "/", &Method::Get)
4617            .expect("the listener must route example.com");
4618        route
4619            .headers_response
4620            .iter()
4621            .find(|edit| edit.key.eq_ignore_ascii_case(b"strict-transport-security"))
4622            .map(|edit| String::from_utf8_lossy(&edit.val).into_owned())
4623    }
4624
4625    fn patch_hsts(proxy: &HttpsProxy, token: usize, address: SocketAddress, hsts: HstsConfig) {
4626        proxy.listeners[&Token(token)]
4627            .borrow_mut()
4628            .update_config(&UpdateHttpsListenerConfig {
4629                address,
4630                hsts: Some(hsts),
4631                ..Default::default()
4632            })
4633            .expect("patch the listener HSTS default");
4634    }
4635
4636    /// A patch the worker refuses leaves the HTTPS listener exactly as it was
4637    /// (sozu-proxy/sozu#1703): its configuration, its rustls context, its
4638    /// answer registry and the HSTS its frontends serve. Each patch carries
4639    /// fields that validate — an ALPN change among them, which rebuilds the
4640    /// rustls context — then one refused field: an `hsts` block without
4641    /// `enabled`, which the old code checked after committing every other
4642    /// field, the new ALPN context and the new templates, or an answer
4643    /// template that does not parse.
4644    ///
4645    /// To SEE THIS RED: in `HttpsListener::update_config`, move the
4646    /// `HstsEnabledRequired` check back to the HSTS block at the end, and
4647    /// write the fields into `self.config` instead of the candidate copy.
4648    #[test]
4649    fn a_rejected_patch_leaves_the_listener_unchanged() {
4650        use sozu_command::proto::command::{AlpnProtocols, UpdateHttpsListenerConfig};
4651
4652        let mut proxy = proxy();
4653        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4654        add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4655        proxy
4656            .add_https_frontend(tagged_front(address))
4657            .expect("add the frontend");
4658
4659        let valid_prefix = UpdateHttpsListenerConfig {
4660            address,
4661            front_timeout: Some(99),
4662            sozu_id_header: Some("X-Edge-Id".to_owned()),
4663            alpn_protocols: Some(AlpnProtocols {
4664                values: vec!["http/1.1".to_owned()],
4665            }),
4666            answers: BTreeMap::from([(
4667                "404".to_owned(),
4668                "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_owned(),
4669            )]),
4670            ..Default::default()
4671        };
4672        let rejected = [
4673            UpdateHttpsListenerConfig {
4674                hsts: Some(HstsConfig {
4675                    enabled: None,
4676                    max_age: Some(200),
4677                    ..Default::default()
4678                }),
4679                ..valid_prefix.clone()
4680            },
4681            UpdateHttpsListenerConfig {
4682                answers: BTreeMap::from([(
4683                    "503".to_owned(),
4684                    "not a valid http response".to_owned(),
4685                )]),
4686                hsts: Some(hsts(200)),
4687                ..valid_prefix.clone()
4688            },
4689        ];
4690        for patch in rejected {
4691            let listener = &proxy.listeners[&Token(0)];
4692            let config_before = listener.borrow().config.clone();
4693            let rustls_before = listener.borrow().rustls_details.clone();
4694            let answers_before = listener.borrow().get_answers().clone();
4695            listener
4696                .borrow_mut()
4697                .update_config(&patch)
4698                .expect_err("the patch must be refused");
4699            let listener = listener.borrow();
4700            assert_eq!(
4701                listener.config, config_before,
4702                "a refused patch must leave the listener configuration unchanged"
4703            );
4704            assert!(
4705                Arc::ptr_eq(&rustls_before, &listener.rustls_details),
4706                "a refused patch must not publish a new rustls context"
4707            );
4708            assert!(
4709                Rc::ptr_eq(&answers_before, listener.get_answers()),
4710                "a refused patch must not publish a new answer registry"
4711            );
4712            drop(listener);
4713            assert_eq!(
4714                served_hsts(&proxy, 0).as_deref(),
4715                Some("max-age=100"),
4716                "a refused patch must not refresh the frontends' HSTS"
4717            );
4718        }
4719
4720        proxy.listeners[&Token(0)]
4721            .borrow_mut()
4722            .update_config(&valid_prefix)
4723            .expect("the valid prefix alone must apply");
4724        let listener = proxy.listeners[&Token(0)].borrow();
4725        assert_eq!(listener.config.front_timeout, 99);
4726        assert_eq!(listener.config.alpn_protocols, vec!["http/1.1".to_owned()]);
4727    }
4728
4729    /// A policy-carrying frontend with no `hsts` of its own, added while the
4730    /// listener on its address had no HSTS default, is copied into a listener
4731    /// added later on that address. Each listener serves its own default: the
4732    /// new listener its HSTS header, the first one none (sozu-proxy/sozu#1691).
4733    #[test]
4734    fn a_copied_frontend_without_hsts_inherits_the_new_listener_default() {
4735        let mut proxy = proxy();
4736        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4737        add_with_hsts(&mut proxy, address, None, 0, None);
4738        proxy
4739            .add_https_frontend(tagged_front(address))
4740            .expect("add the frontend");
4741        add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(100)));
4742
4743        assert_eq!(served_hsts(&proxy, 0), None);
4744        assert_eq!(
4745            served_hsts(&proxy, 1).as_deref(),
4746            Some("max-age=100"),
4747            "the listener added after the frontend must serve its own HSTS default"
4748        );
4749
4750        // Both copies keep following their own listener's default.
4751        patch_hsts(&proxy, 0, address, hsts(200));
4752        patch_hsts(&proxy, 1, address, hsts(300));
4753        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=200"));
4754        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=300"));
4755    }
4756
4757    /// A frontend with its own `hsts` keeps it on every listener of its
4758    /// address, whatever their defaults, at copy time and on later patches.
4759    #[test]
4760    fn a_frontend_with_explicit_hsts_is_never_overridden_by_a_listener_default() {
4761        let mut proxy = proxy();
4762        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4763        add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4764        proxy
4765            .add_https_frontend(RequestHttpFrontend {
4766                hsts: Some(hsts(42)),
4767                ..tagged_front(address)
4768            })
4769            .expect("add the frontend");
4770        add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(300)));
4771
4772        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4773        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4774
4775        patch_hsts(&proxy, 0, address, hsts(500));
4776        patch_hsts(&proxy, 1, address, hsts(600));
4777        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4778        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4779    }
4780
4781    /// On a single listener, patching the HSTS default reaches a
4782    /// policy-carrying frontend that declared no `hsts` of its own, whether
4783    /// the listener had a default when the frontend was added or not.
4784    #[test]
4785    fn a_listener_hsts_patch_refreshes_every_frontend_without_its_own_hsts() {
4786        for initial in [None, Some(hsts(100))] {
4787            let mut proxy = proxy();
4788            let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4789            add_with_hsts(&mut proxy, address, None, 0, initial);
4790            proxy
4791                .add_https_frontend(tagged_front(address))
4792                .expect("add the frontend");
4793
4794            patch_hsts(&proxy, 0, address, hsts(700));
4795            assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=700"));
4796
4797            patch_hsts(
4798                &proxy,
4799                0,
4800                address,
4801                HstsConfig {
4802                    enabled: Some(false),
4803                    ..Default::default()
4804                },
4805            );
4806            assert_eq!(served_hsts(&proxy, 0), None);
4807        }
4808    }
4809
4810    /// A self-signed cert usable as a CA root anchor in the verifier tests.
4811    const TEST_CA_PEM: &str = include_str!("../assets/certificate.pem");
4812
4813    fn test_crypto_provider() -> Arc<CryptoProvider> {
4814        Arc::new(default_provider())
4815    }
4816
4817    fn https_config_with_client_auth(
4818        mode: ClientAuthMode,
4819        cas: &[&str],
4820        crls: &[&str],
4821    ) -> HttpsListenerConfig {
4822        let address = SocketAddress::new_v4(127, 0, 0, 1, 1049);
4823        let mut config = ListenerBuilder::new_https(address)
4824            .to_tls(None)
4825            .expect("default HTTPS listener config");
4826        config.client_auth = Some(mode as i32);
4827        config.client_ca_certificates = cas.iter().map(|s| s.to_string()).collect();
4828        config.client_ca_crls = crls.iter().map(|s| s.to_string()).collect();
4829        config
4830    }
4831
4832    #[test]
4833    fn client_cert_verifier_none_is_disabled() {
4834        // NONE (and an absent field) must keep the no-client-auth path.
4835        let provider = test_crypto_provider();
4836        let config = https_config_with_client_auth(ClientAuthMode::ClientAuthNone, &[], &[]);
4837        assert!(
4838            HttpsListener::client_cert_verifier(&config, &provider)
4839                .expect("verifier build")
4840                .is_none()
4841        );
4842
4843        let mut absent = config;
4844        absent.client_auth = None;
4845        assert!(
4846            HttpsListener::client_cert_verifier(&absent, &provider)
4847                .expect("verifier build")
4848                .is_none(),
4849            "absent client_auth must decode to NONE"
4850        );
4851    }
4852
4853    #[test]
4854    fn client_cert_verifier_rejects_unknown_mode() {
4855        // An unknown (future/garbage) enum value must be rejected, never fold
4856        // to NONE and silently accept unauthenticated clients.
4857        let provider = test_crypto_provider();
4858        let mut config =
4859            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4860        config.client_auth = Some(999);
4861        assert!(matches!(
4862            HttpsListener::client_cert_verifier(&config, &provider),
4863            Err(ListenerError::ClientAuth(_))
4864        ));
4865    }
4866
4867    #[test]
4868    fn client_cert_verifier_required_builds_with_ca() {
4869        let provider = test_crypto_provider();
4870        let config =
4871            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4872        assert!(
4873            HttpsListener::client_cert_verifier(&config, &provider)
4874                .expect("verifier build")
4875                .is_some()
4876        );
4877    }
4878
4879    #[test]
4880    fn client_cert_verifier_optional_builds_with_ca() {
4881        let provider = test_crypto_provider();
4882        let config =
4883            https_config_with_client_auth(ClientAuthMode::ClientAuthOptional, &[TEST_CA_PEM], &[]);
4884        assert!(
4885            HttpsListener::client_cert_verifier(&config, &provider)
4886                .expect("verifier build")
4887                .is_some()
4888        );
4889    }
4890
4891    #[test]
4892    fn client_cert_verifier_requires_a_trusted_ca() {
4893        // A non-NONE mode with an empty CA bundle is a configuration error, not
4894        // a silently-permissive listener.
4895        let provider = test_crypto_provider();
4896        let config = https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[], &[]);
4897        assert!(matches!(
4898            HttpsListener::client_cert_verifier(&config, &provider),
4899            Err(ListenerError::ClientAuth(_))
4900        ));
4901    }
4902
4903    #[test]
4904    fn client_cert_verifier_rejects_malformed_ca() {
4905        let provider = test_crypto_provider();
4906        let config = https_config_with_client_auth(
4907            ClientAuthMode::ClientAuthRequired,
4908            &["not a pem certificate"],
4909            &[],
4910        );
4911        // A CA entry that parses to zero anchors is a configuration error.
4912        assert!(matches!(
4913            HttpsListener::client_cert_verifier(&config, &provider),
4914            Err(ListenerError::ClientAuth(_))
4915        ));
4916    }
4917
4918    #[test]
4919    fn client_cert_verifier_rejects_empty_ca_entry_among_valid_ones() {
4920        // A valid CA followed by an entry contributing no certificate must be
4921        // rejected, not silently dropped, so the configured trust set is never
4922        // quietly reduced to a subset.
4923        let provider = test_crypto_provider();
4924        let config = https_config_with_client_auth(
4925            ClientAuthMode::ClientAuthRequired,
4926            &[TEST_CA_PEM, "not a certificate"],
4927            &[],
4928        );
4929        assert!(matches!(
4930            HttpsListener::client_cert_verifier(&config, &provider),
4931            Err(ListenerError::ClientAuth(_))
4932        ));
4933    }
4934
4935    #[test]
4936    fn client_cert_verifier_rejects_empty_crl_entry() {
4937        // A configured CRL entry that yields zero CRLs would silently disable
4938        // revocation; it must be rejected rather than skipped.
4939        let provider = test_crypto_provider();
4940        let config = https_config_with_client_auth(
4941            ClientAuthMode::ClientAuthRequired,
4942            &[TEST_CA_PEM],
4943            &["not a crl"],
4944        );
4945        assert!(matches!(
4946            HttpsListener::client_cert_verifier(&config, &provider),
4947            Err(ListenerError::ClientAuth(_))
4948        ));
4949    }
4950
4951    // ── Client certificate revocation ──
4952    //
4953    // The fixtures come from `lib/assets/mtls/generate.sh`: one CA, one
4954    // client it issued, and CRLs that each leave exactly one reason to accept
4955    // or reject that client. The verdict is checked on the rustls error, not
4956    // only on accept/reject, because a handshake rejected for the wrong
4957    // reason would still look like a pass at the TLS level.
4958
4959    const MTLS_CA_PEM: &str = include_str!("../assets/mtls/ca-cert.pem");
4960    const MTLS_CLIENT_PEM: &str = include_str!("../assets/mtls/client-cert.pem");
4961    /// Issued by the CA, nextUpdate in 2125, revokes nothing.
4962    const MTLS_CRL_CURRENT: &str = include_str!("../assets/mtls/crl-current.pem");
4963    /// Issued by the CA, nextUpdate in 2125, revokes the client.
4964    const MTLS_CRL_REVOKED: &str = include_str!("../assets/mtls/crl-revoked.pem");
4965    /// Issued by the CA, nextUpdate in January 2020, revokes nothing.
4966    const MTLS_CRL_EXPIRED: &str = include_str!("../assets/mtls/crl-expired.pem");
4967    /// Issued by another CA: covers nothing in the client's chain.
4968    const MTLS_CRL_OTHER_CA: &str = include_str!("../assets/mtls/crl-other-ca.pem");
4969    /// Two partitions of the CA's list, each with its own
4970    /// IssuingDistributionPoint, neither covering the client.
4971    const MTLS_CRL_PARTITION_A: &str = include_str!("../assets/mtls/crl-partition-a.pem");
4972    const MTLS_CRL_PARTITION_B: &str = include_str!("../assets/mtls/crl-partition-b.pem");
4973
4974    /// The verdict of a `required` listener trusting the test CA with `crls`
4975    /// on the test client's certificate, at the current time.
4976    fn verify_mtls_client(crls: &[&str]) -> Result<(), rustls::Error> {
4977        verify_mtls_client_at(crls, rustls::pki_types::UnixTime::now())
4978    }
4979
4980    /// The same verdict, for a handshake at `time`.
4981    fn verify_mtls_client_at(
4982        crls: &[&str],
4983        time: rustls::pki_types::UnixTime,
4984    ) -> Result<(), rustls::Error> {
4985        let config =
4986            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4987        let verifier = HttpsListener::client_cert_verifier(&config, &test_crypto_provider())
4988            .expect("a CA and well-formed CRLs must build a verifier")
4989            .expect("required client auth must install a verifier");
4990        let client = CertificateDer::from_pem_slice(MTLS_CLIENT_PEM.as_bytes())
4991            .expect("the client fixture is a PEM certificate");
4992        verifier.verify_client_cert(&client, &[], time).map(|_| ())
4993    }
4994
4995    /// Whether a `required` listener can be built with `crls`, and the
4996    /// reason when it cannot.
4997    fn build_mtls_listener(crls: &[&str]) -> Result<(), String> {
4998        let config =
4999            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
5000        match HttpsListener::client_cert_verifier(&config, &test_crypto_provider()) {
5001            Ok(_) => Ok(()),
5002            Err(ListenerError::ClientAuth(reason)) => Err(reason),
5003            Err(other) => panic!("unexpected listener error {other:?}"),
5004        }
5005    }
5006
5007    #[test]
5008    fn client_revocation_accepts_a_client_no_crl_revokes() {
5009        assert_eq!(
5010            verify_mtls_client(&[]),
5011            Ok(()),
5012            "precondition: the client chains to the CA"
5013        );
5014        assert_eq!(
5015            verify_mtls_client(&[MTLS_CRL_CURRENT]),
5016            Ok(()),
5017            "a current CRL from the client's issuer that does not list it must not reject it"
5018        );
5019    }
5020
5021    #[test]
5022    fn client_revocation_rejects_a_revoked_client() {
5023        // The CRL is current, so only the serial lookup can reject the client.
5024        // Without `with_crls`, the client would be accepted.
5025        assert_eq!(
5026            verify_mtls_client(&[MTLS_CRL_REVOKED]),
5027            Err(rustls::Error::InvalidCertificate(
5028                rustls::CertificateError::Revoked
5029            ))
5030        );
5031    }
5032
5033    #[test]
5034    fn client_revocation_refuses_an_expired_crl_only_where_the_operator_states_it() {
5035        // The main process refuses a CRL already past its nextUpdate in what
5036        // an operator states: it would reject every client it covers.
5037        let now = i64::try_from(
5038            std::time::SystemTime::now()
5039                .duration_since(std::time::UNIX_EPOCH)
5040                .expect("a clock after 1970")
5041                .as_secs(),
5042        )
5043        .expect("a clock within i64");
5044        let refused = HttpsListener::check_crls_current(&[MTLS_CRL_EXPIRED.to_owned()], now);
5045        assert!(
5046            matches!(refused, Err(ListenerError::ClientAuth(ref reason)) if reason.contains("expired")),
5047            "an expired CRL must be refused when stated, got {refused:?}"
5048        );
5049        assert!(
5050            HttpsListener::check_crls_current(
5051                &[MTLS_CRL_CURRENT.to_owned(), MTLS_CRL_OTHER_CA.to_owned()],
5052                now
5053            )
5054            .is_ok()
5055        );
5056        // A worker still builds a listener whose CRL lapsed while it ran (a
5057        // restarted worker replaying the state), so a later reload has a
5058        // listener to patch; the lapsed CRL rejects clients at handshake.
5059        assert_eq!(build_mtls_listener(&[MTLS_CRL_EXPIRED]), Ok(()));
5060        let config = https_config_with_client_auth(
5061            ClientAuthMode::ClientAuthRequired,
5062            &[MTLS_CA_PEM],
5063            &[MTLS_CRL_EXPIRED],
5064        );
5065        assert!(HttpsListener::try_new(config, Token(0)).is_ok());
5066    }
5067
5068    #[test]
5069    fn client_revocation_rejects_a_client_once_its_crl_has_lapsed() {
5070        // A CRL current when the listener was built lapses while it runs.
5071        // In June 2125 the current CRL (nextUpdate January 2125) has lapsed
5072        // and the client certificate (notAfter 2126) has not, so only the
5073        // CRL expiry can reject the client. Without
5074        // `enforce_revocation_expiration`, rustls keeps trusting the stale CRL
5075        // and accepts it.
5076        let june_2125 =
5077            rustls::pki_types::UnixTime::since_unix_epoch(Duration::from_secs(4_905_100_800));
5078        let verdict = verify_mtls_client_at(&[MTLS_CRL_CURRENT], june_2125);
5079        assert!(
5080            matches!(
5081                verdict,
5082                Err(rustls::Error::InvalidCertificate(
5083                    rustls::CertificateError::ExpiredRevocationList
5084                        | rustls::CertificateError::ExpiredRevocationListContext { .. }
5085                ))
5086            ),
5087            "a CRL past its nextUpdate must reject the client, got {verdict:?}"
5088        );
5089        // Positive space: without CRLs the client is still valid then, so the
5090        // rejection above is the CRL expiry and nothing else.
5091        assert_eq!(verify_mtls_client_at(&[], june_2125), Ok(()));
5092    }
5093
5094    #[test]
5095    fn client_revocation_rejects_a_client_no_configured_crl_covers() {
5096        // Once any CRL is configured, a client whose issuer no CRL covers has
5097        // an unknown revocation status, which rustls's default
5098        // `UnknownStatusPolicy::Deny` rejects.
5099        assert_eq!(
5100            verify_mtls_client(&[MTLS_CRL_OTHER_CA]),
5101            Err(rustls::Error::InvalidCertificate(
5102                rustls::CertificateError::UnknownRevocationStatus
5103            ))
5104        );
5105        // Adding the issuer's own current CRL restores the verdict.
5106        assert_eq!(
5107            verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_CURRENT]),
5108            Ok(())
5109        );
5110    }
5111
5112    #[test]
5113    fn client_revocation_refuses_two_crls_of_one_issuer_with_overlapping_scopes() {
5114        // webpki would only read the first of them, so the order alone would
5115        // decide: listing the revoking CRL second accepts the revoked client.
5116        // Every order must fail the listener instead.
5117        for crls in [
5118            vec![MTLS_CRL_CURRENT, MTLS_CRL_REVOKED],
5119            vec![MTLS_CRL_REVOKED, MTLS_CRL_CURRENT],
5120            // A full CRL covers every partition of its CA.
5121            vec![MTLS_CRL_CURRENT, MTLS_CRL_PARTITION_A],
5122            vec![MTLS_CRL_PARTITION_A, MTLS_CRL_CURRENT],
5123            // The same partition twice.
5124            vec![MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_A],
5125        ] {
5126            let refused = build_mtls_listener(&crls);
5127            assert!(
5128                refused
5129                    .as_ref()
5130                    .is_err_and(|reason| reason.contains("overlapping")),
5131                "overlapping CRLs of one issuer must fail the listener, got {refused:?}"
5132            );
5133        }
5134        // Two CRLs in one configured entry count the same as two entries.
5135        assert!(build_mtls_listener(&[&format!("{MTLS_CRL_CURRENT}{MTLS_CRL_REVOKED}")]).is_err());
5136    }
5137
5138    #[test]
5139    fn client_revocation_takes_the_partitions_of_one_issuer() {
5140        // Partitions told apart by their distribution point cover disjoint
5141        // certificates, so a CA publishing its list that way stays usable.
5142        assert_eq!(
5143            build_mtls_listener(&[MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_B]),
5144            Ok(())
5145        );
5146    }
5147
5148    #[test]
5149    fn client_revocation_takes_one_crl_per_issuer() {
5150        // Positive space of the rule above: CRLs from distinct issuers are
5151        // all kept, and none shadows another.
5152        assert_eq!(
5153            verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_REVOKED]),
5154            Err(rustls::Error::InvalidCertificate(
5155                rustls::CertificateError::Revoked
5156            ))
5157        );
5158        assert_eq!(
5159            verify_mtls_client(&[MTLS_CRL_REVOKED, MTLS_CRL_OTHER_CA]),
5160            Err(rustls::Error::InvalidCertificate(
5161                rustls::CertificateError::Revoked
5162            ))
5163        );
5164    }
5165
5166    /// The in-place refresh of a running listener's client authentication
5167    /// policy: the patch replaces the policy and rebuilds the rustls context
5168    /// that holds the verifier; a policy the verifier cannot be built from
5169    /// leaves the listener exactly as it was. Its effect on handshakes is
5170    /// covered by `test_mtls_policy_patch_*` in `e2e/src/tests/tls_tests.rs`.
5171    #[test]
5172    fn a_client_auth_policy_patch_rebuilds_the_listener_verifier() {
5173        let config = https_config_with_client_auth(
5174            ClientAuthMode::ClientAuthRequired,
5175            &[MTLS_CA_PEM],
5176            &[MTLS_CRL_CURRENT],
5177        );
5178        let mut listener =
5179            HttpsListener::try_new(config.clone(), Token(0)).expect("build the mTLS listener");
5180        let original_context = listener.rustls_details.clone();
5181
5182        let refreshed = HttpsListenerConfig {
5183            client_ca_crls: vec![MTLS_CRL_REVOKED.to_owned()],
5184            ..config.clone()
5185        }
5186        .client_auth_policy();
5187        listener
5188            .update_config(&UpdateHttpsListenerConfig {
5189                address: config.address,
5190                client_auth_policy: Some(refreshed.clone()),
5191                ..Default::default()
5192            })
5193            .expect("a buildable policy must be applied");
5194        assert_eq!(listener.config.client_auth_policy(), refreshed);
5195        assert!(
5196            !Arc::ptr_eq(&listener.rustls_details, &original_context),
5197            "the verifier lives in the rustls context, which must be rebuilt"
5198        );
5199
5200        let applied_context = listener.rustls_details.clone();
5201        let unbuildable = ClientAuthPolicy {
5202            client_auth: Some(ClientAuthMode::ClientAuthRequired as i32),
5203            client_ca_certificates: vec![],
5204            client_ca_crls: vec![],
5205        };
5206        assert!(
5207            listener
5208                .update_config(&UpdateHttpsListenerConfig {
5209                    address: config.address,
5210                    client_auth_policy: Some(unbuildable.clone()),
5211                    ..Default::default()
5212                })
5213                .is_err(),
5214            "required client auth with no trusted CA must be refused"
5215        );
5216        assert!(HttpsListener::validate_client_auth_policy(&unbuildable).is_err());
5217        assert_eq!(
5218            listener.config.client_auth_policy(),
5219            refreshed,
5220            "a refused policy leaves the applied one in place"
5221        );
5222        assert!(Arc::ptr_eq(&listener.rustls_details, &applied_context));
5223    }
5224}