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::{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 last_event(&self) -> Instant {
1239        self.last_event
1240    }
1241
1242    fn print_session(&self) {
1243        self.state.print_state("HTTPS");
1244        error!("{} Metrics: {:?}", log_context!(self), self.metrics);
1245    }
1246
1247    fn frontend_token(&self) -> Token {
1248        self.frontend_token
1249    }
1250}
1251
1252pub type HostName = String;
1253pub type PathBegin = String;
1254
1255pub struct HttpsListener {
1256    active: bool,
1257    address: StdSocketAddr,
1258    answers: Rc<RefCell<HttpAnswers>>,
1259    config: HttpsListenerConfig,
1260    fronts: Router,
1261    listener: Option<MioTcpListener>,
1262    /// A socket this listener holds but has NOT registered.
1263    ///
1264    /// `activate()` parks an inherited socket here when `Registry::register`
1265    /// fails, instead of letting the `?` drop and close a descriptor
1266    /// `Listeners::get_*` has already removed from the SCM table (sozu#1342).
1267    /// The next activation reuses it rather than binding a second one.
1268    ///
1269    /// Deliberately NOT `listener`: every other consumer reads that field's `Some` as
1270    /// "registered and live" — `soft_stop` and `hard_stop` deregister it and
1271    /// report the failure, `give_back_listener` hands it back as an activated
1272    /// socket, `accept` accepts on it. A parked socket is none of those things,
1273    /// and putting it there made a failed registration answer `ENOENT` from
1274    /// `deregister` and fail the whole soft stop.
1275    parked_listener: Option<MioTcpListener>,
1276    /// Set by `activate()`, cleared by the first `accept()` that answers
1277    /// `WouldBlock`: while set, `accept()` sets `TCP_NODELAY` on each socket
1278    /// it returns, because a connection queued before `activate()` set the
1279    /// flag on the listener did not inherit it. See the field of the same
1280    /// name on `HttpListener` (`lib/src/http.rs`).
1281    nodelay_backlog: bool,
1282    resolver: Arc<MutexCertificateResolver>,
1283    rustls_details: Arc<RustlsServerConfig>,
1284    tags: BTreeMap<String, CachedTags>,
1285    token: Token,
1286}
1287
1288impl ListenerHandler for HttpsListener {
1289    fn get_addr(&self) -> &StdSocketAddr {
1290        &self.address
1291    }
1292
1293    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
1294        self.tags.get(key)
1295    }
1296
1297    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
1298        match tags {
1299            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
1300            None => self.tags.remove(&key),
1301        };
1302    }
1303
1304    fn protocol(&self) -> Protocol {
1305        Protocol::HTTPS
1306    }
1307
1308    fn public_address(&self) -> StdSocketAddr {
1309        self.config
1310            .public_address
1311            .map(|addr| addr.into())
1312            .unwrap_or(self.address)
1313    }
1314}
1315
1316impl L7ListenerHandler for HttpsListener {
1317    fn get_sticky_name(&self) -> &str {
1318        &self.config.sticky_name
1319    }
1320
1321    fn get_sozu_id_header(&self) -> &str {
1322        self.config
1323            .sozu_id_header
1324            .as_deref()
1325            .filter(|s| !s.is_empty())
1326            .unwrap_or("Sozu-Id")
1327    }
1328
1329    fn get_connect_timeout(&self) -> u32 {
1330        self.config.connect_timeout
1331    }
1332
1333    fn frontend_from_request(
1334        &self,
1335        host: &str,
1336        uri: &str,
1337        method: &Method,
1338    ) -> Result<RouteResult, FrontendFromRequestError> {
1339        let start = Instant::now();
1340        let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
1341            Ok(tuple) => tuple,
1342            Err(parse_error) => {
1343                // parse_error contains a slice of given_host, which should NOT escape this scope
1344                return Err(FrontendFromRequestError::HostParse {
1345                    host: host.to_owned(),
1346                    error: parse_error.to_string(),
1347                });
1348            }
1349        };
1350
1351        if remaining_input != &b""[..] {
1352            return Err(FrontendFromRequestError::InvalidCharsAfterHost(
1353                host.to_owned(),
1354            ));
1355        }
1356
1357        // it is alright to call from_utf8_unchecked,
1358        // we already verified that there are only ascii
1359        // chars in there
1360        // SAFETY: `hostname` was just produced by `hostname_and_port`
1361        // (`lib/src/protocol/kawa_h1/parser.rs`), which only accepts
1362        // bytes matching `is_hostname_char` (alphanumeric, `-`, `.`, plus
1363        // `_` under the tolerant-http1-parser feature). All accepted
1364        // bytes are ASCII (≤ 0x7F), so the slice is valid single-byte UTF-8.
1365        let host = unsafe { from_utf8_unchecked(hostname) };
1366
1367        let route = self.fronts.lookup(host, uri, method).map_err(|e| {
1368            incr!(names::http::FAILED_BACKEND_MATCHING);
1369            FrontendFromRequestError::NoClusterFound(e)
1370        })?;
1371
1372        let now = Instant::now();
1373
1374        if let Some(cluster) = route.cluster_id.as_deref() {
1375            time!(
1376                names::event_loop::FRONTEND_MATCHING_TIME,
1377                cluster,
1378                (now - start).as_millis()
1379            );
1380        }
1381
1382        Ok(route)
1383    }
1384
1385    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
1386        &self.answers
1387    }
1388
1389    fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
1390        // `H2FloodConfig`'s fields are private and `from_optional` applies the
1391        // `.max(1)` clamp `new` carries (sozu-proxy/sozu#1418). This used to be
1392        // a raw struct literal, which skipped that clamp on the only path that
1393        // carries operator-set thresholds: config load rejects an out-of-range
1394        // knob (`ConfigError::H2ThresholdBelowMinimum`), and the clamp is the
1395        // structural backstop for a raw protobuf `AddHttpsListener` or a
1396        // state file saved before that gate existed.
1397        crate::protocol::mux::H2FloodConfig::from_optional(
1398            self.config.h2_max_rst_stream_per_window,
1399            self.config.h2_max_ping_per_window,
1400            self.config.h2_max_settings_per_window,
1401            self.config.h2_max_empty_data_per_window,
1402            self.config.h2_max_window_update_stream0_per_window,
1403            self.config.h2_max_continuation_frames,
1404            self.config.h2_max_glitch_count,
1405            self.config.h2_max_rst_stream_lifetime,
1406            self.config.h2_max_rst_stream_abusive_lifetime,
1407            self.config.h2_max_rst_stream_emitted_lifetime,
1408            self.config.h2_max_header_list_size,
1409            self.config.h2_max_header_table_size,
1410            self.config.h2_max_header_fields,
1411            self.config.h2_stream_refusal_percent,
1412        )
1413    }
1414
1415    fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1416        crate::protocol::mux::H2ConnectionConfig::from_optional(
1417            self.config.h2_initial_connection_window,
1418            self.config.h2_max_concurrent_streams,
1419            self.config.h2_stream_shrink_ratio,
1420        )
1421    }
1422
1423    fn get_strict_sni_binding(&self) -> bool {
1424        // SNI↔:authority binding is enforced by default (closes
1425        // CWE-346 / CWE-444); this listener knob preserves that
1426        // behavior by default and lets operators opt out when cross-SNI
1427        // routing is intentional.
1428        //
1429        // Note: `strict_sni_binding = false` theoretically allows an
1430        // attacker to present many distinct SNIs on the same TCP
1431        // connection. rustls 0.23 **bans TLS renegotiation outright** (see
1432        // `rustls::server::ClientHello` which is consumed during the initial
1433        // handshake only), so a single TCP connection gets exactly one SNI
1434        // for its lifetime — the cross-SNI-flood vector is not reachable in
1435        // practice. Kept documented here so a future rustls upgrade that
1436        // reintroduces renegotiation (vanishingly unlikely) surfaces the
1437        // assumption during review.
1438        self.config.strict_sni_binding.unwrap_or(true)
1439    }
1440
1441    fn get_elide_x_real_ip(&self) -> bool {
1442        self.config.elide_x_real_ip.unwrap_or(false)
1443    }
1444
1445    fn get_send_x_real_ip(&self) -> bool {
1446        self.config.send_x_real_ip.unwrap_or(false)
1447    }
1448
1449    fn get_forwarded_headers(&self) -> ForwardedHeaders {
1450        // An unknown wire value reads as the default, like prost's own
1451        // getter; `update_config` and the main-process state refuse one.
1452        self.config.forwarded_headers()
1453    }
1454
1455    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1456        // Inherit `back_timeout` when the knob is unset so listeners tuned for
1457        // long-running backends do not cancel streams at the 30 s security
1458        // floor. The `max(30, …)` keeps the baseline slow-multiplex mitigation
1459        // when `back_timeout` is shorter than 30 s. Explicit values (including
1460        // ones below 30 s) win — operators under a slow-multiplex attack can
1461        // lower the per-stream deadline to cap buffer pinning.
1462        let seconds = self
1463            .config
1464            .h2_stream_idle_timeout_seconds
1465            .map(|s| u64::from(s.max(1)))
1466            .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1467        std::time::Duration::from_secs(seconds)
1468    }
1469
1470    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1471        match self.config.h2_graceful_shutdown_deadline_seconds {
1472            None => Some(std::time::Duration::from_secs(5)),
1473            Some(0) => None,
1474            Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1475        }
1476    }
1477}
1478
1479impl HttpsListener {
1480    /// This listener's identity: its address, plus the network interface its
1481    /// socket is bound to when it has one.
1482    pub fn key(&self) -> ListenerKey {
1483        ListenerKey::new(self.address, self.config.interface.as_deref())
1484    }
1485
1486    /// Whether this listener is the one `key` names.
1487    pub fn is(&self, key: &ListenerKey) -> bool {
1488        key.matches(&self.address, self.config.interface.as_deref())
1489    }
1490
1491    /// Whether this listener rejects clients that do not negotiate `h2`
1492    /// via TLS ALPN (including those that omit ALPN). Reads the
1493    /// `disable_http11` knob; defaults to `false` to preserve the
1494    /// historical behavior where a missing ALPN silently downgrades
1495    /// to HTTP/1.1.
1496    pub fn is_http11_disabled(&self) -> bool {
1497        self.config.disable_http11.unwrap_or(false)
1498    }
1499
1500    /// Borrow the listener's certificate resolver. Used by the TLS handshake
1501    /// path to snapshot the SAN set of the certificate Sōzu serves for a
1502    /// given SNI, so the H2 router can accept connection coalescing
1503    /// (RFC 7540 §9.1.1 / RFC 9113 §9.1.1) on every authority covered by
1504    /// that cert (RFC 6125 §6.4.3 wildcard handling).
1505    pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1506        &self.resolver
1507    }
1508
1509    pub fn try_new(
1510        config: HttpsListenerConfig,
1511        token: Token,
1512    ) -> Result<HttpsListener, ListenerError> {
1513        // Defense in depth, as `update_config`: the main-process state
1514        // refuses an unknown mode, a raw protobuf client may not.
1515        if let Some(v) = config.forwarded_headers {
1516            validate_forwarded_headers(v)?;
1517        }
1518        let resolver = Arc::new(MutexCertificateResolver::default());
1519
1520        let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1521
1522        let answers = Self::build_answers(&config)?;
1523
1524        Ok(HttpsListener {
1525            listener: None,
1526            parked_listener: None,
1527            nodelay_backlog: false,
1528            address: config.address.into(),
1529            resolver,
1530            rustls_details: server_config,
1531            active: false,
1532            fronts: Router::new(),
1533            answers: Rc::new(RefCell::new(answers)),
1534            config,
1535            token,
1536            tags: BTreeMap::new(),
1537        })
1538    }
1539
1540    /// Build the listener's HTTP answer templates from its config, reconciling
1541    /// the legacy `http_answers` per-status fields with the new `answers`
1542    /// template map (the new map wins on collision; legacy fields fill any
1543    /// status not yet migrated). Shared by [`Self::try_new`] and
1544    /// [`Self::validate_config`] so the master-side pre-commit check cannot
1545    /// drift from worker construction.
1546    fn build_answers(config: &HttpsListenerConfig) -> Result<HttpAnswers, ListenerError> {
1547        let mut answers_map = config.answers.clone();
1548        if let Some(ref legacy) = config.http_answers {
1549            crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1550        }
1551        HttpAnswers::new(&answers_map)
1552            .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1553    }
1554
1555    /// Validate that a worker can build this HTTPS listener configuration — the
1556    /// rustls context and the answer templates — WITHOUT constructing the full
1557    /// listener or binding a socket. Runs the exact fallible steps of
1558    /// [`Self::try_new`] (via the shared [`Self::create_rustls_context`] /
1559    /// `Self::build_answers` helpers), so master-side validation is faithful
1560    /// to what the worker will do.
1561    ///
1562    /// The main process calls this before committing an `AddHttpsListener` to
1563    /// its `ConfigState` and fanning it out, so an invalid listener never
1564    /// reserves its address and blocks a corrected reload (sozu#1301).
1565    pub fn validate_config(config: &HttpsListenerConfig) -> Result<(), ListenerError> {
1566        // Buildability only; the H2 knob floors are enforced beside their
1567        // audience in `bin/src/command/requests.rs::validate_h2_knob_floors`
1568        // (sozu#1418), because a worker CAN build an out-of-range listener —
1569        // `H2FloodConfig::from_optional` clamps it.
1570        //
1571        // An empty resolver matches `try_new`: the default certificate is added
1572        // later via `AddCertificate`, so only the config-level TLS parameters
1573        // (versions, ciphers, groups, ALPN) and the answer templates are
1574        // exercised here.
1575        Self::create_rustls_context(config, Arc::new(MutexCertificateResolver::default()))?;
1576        Self::build_answers(config)?;
1577        Ok(())
1578    }
1579
1580    pub fn activate(
1581        &mut self,
1582        registry: &Registry,
1583        tcp_listener: Option<MioTcpListener>,
1584    ) -> Result<Token, ListenerError> {
1585        if self.active {
1586            return Ok(self.token);
1587        }
1588        let address: StdSocketAddr = self.config.address.into();
1589
1590        // Reuse a socket a previous attempt parked here after a failed
1591        // registration, rather than binding a second one. Taking it
1592        // unconditionally is what keeps the two from coexisting: if the caller
1593        // ALSO handed one over, the parked socket is disposed of deliberately
1594        // below instead of being silently overwritten.
1595        let parked = self.parked_listener.take();
1596        let mut listener = match (tcp_listener, parked) {
1597            (Some(inherited), Some(parked)) => {
1598                // Not reachable while `Listeners::get_*` removes the entry it
1599                // hands over and `scm_listeners` is written once: a parked
1600                // socket means this address's entry was already consumed, so
1601                // the caller cannot have another. Neither of those is enforced
1602                // here, so dispose of the loser explicitly and loudly rather
1603                // than letting an assignment close it — that silent close is
1604                // sozu#1342 itself.
1605                warn!(
1606                    "{} closing the socket parked on the HTTPS listener for {}: an \
1607                     inherited socket arrived for the same address and is the one being \
1608                     activated",
1609                    log_module_context!(),
1610                    address
1611                );
1612                drop(parked);
1613                inherited
1614            }
1615            (Some(inherited), None) => inherited,
1616            (None, Some(parked)) => parked,
1617            (None, None) => server_bind(address, self.config.interface.as_deref()).map_err(
1618                |server_bind_error| ListenerError::Activation {
1619                    address,
1620                    error: server_bind_error.to_string(),
1621                },
1622            )?,
1623        };
1624
1625        // Once per listener, on whichever socket won above — freshly bound,
1626        // inherited over SCM_RIGHTS, or parked. Every socket accepted from now
1627        // on inherits it; see `nodelay_backlog` for the ones already queued.
1628        if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
1629            error!(
1630                "{} error setting nodelay on listen socket({:?}): {:?}",
1631                log_module_context!(),
1632                listener,
1633                e
1634            );
1635        }
1636
1637        let registration = registry
1638            .register(&mut listener, self.token, Interest::READABLE)
1639            .map_err(ListenerError::SocketRegistration);
1640        if let Err(error) = registration {
1641            // Park the socket instead of letting the `?` drop it. `register`
1642            // is fallible (EEXIST for a descriptor still registered because
1643            // `notify_deactivate_listener` only logs a failed deregister,
1644            // EBADF, ENOMEM), and a socket still held in a local is dropped —
1645            // and closed — on the way out. For an SCM-inherited socket that is
1646            // the sozu#1342 close in a second place: `Listeners::get_*` has
1647            // already removed the entry, so nothing else owns the descriptor.
1648            // It goes to `parked_listener`, never to `listener`, because every other
1649            // consumer of that field reads `Some` as "registered and live".
1650            self.parked_listener = Some(listener);
1651            return Err(error);
1652        }
1653
1654        self.listener = Some(listener);
1655        self.nodelay_backlog = true;
1656        self.active = true;
1657        // Post: an activated listener owns a bound socket and is flagged active,
1658        // so a later `activate()` short-circuits on the `self.active` guard and
1659        // `give_back_listener*` find a socket to hand back. The two must move in
1660        // lockstep — an active listener with no socket would silently accept
1661        // nothing.
1662        debug_assert!(
1663            self.active && self.listener.is_some(),
1664            "an activated HTTPS listener must hold a bound socket and be flagged active"
1665        );
1666        Ok(self.token)
1667    }
1668
1669    pub fn create_rustls_context(
1670        config: &HttpsListenerConfig,
1671        resolver: Arc<MutexCertificateResolver>,
1672    ) -> Result<RustlsServerConfig, ListenerError> {
1673        let cipher_names = if config.cipher_list.is_empty() {
1674            DEFAULT_CIPHER_LIST.to_vec()
1675        } else {
1676            config
1677                .cipher_list
1678                .iter()
1679                .map(|s| s.as_str())
1680                .collect::<Vec<_>>()
1681        };
1682
1683        let ciphers = cipher_names
1684            .into_iter()
1685            .filter_map(|cipher| {
1686                cipher_suite_by_name(cipher).or_else(|| {
1687                    error!(
1688                        "{} unknown or unsupported cipher: {:?}",
1689                        log_module_context!(),
1690                        cipher
1691                    );
1692                    None
1693                })
1694            })
1695            .collect::<Vec<_>>();
1696
1697        let versions = config
1698            .versions
1699            .iter()
1700            .filter_map(|version| match TlsVersion::try_from(*version) {
1701                Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1702                Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1703                Ok(other_version) => {
1704                    error!(
1705                        "{} unsupported TLS version {:?}",
1706                        log_module_context!(),
1707                        other_version
1708                    );
1709                    None
1710                }
1711                Err(_) => {
1712                    error!("{} unsupported TLS version", log_module_context!());
1713                    None
1714                }
1715            })
1716            .collect::<Vec<_>>();
1717
1718        let kx_groups = if config.groups_list.is_empty() {
1719            default_provider().kx_groups
1720        } else {
1721            config
1722                .groups_list
1723                .iter()
1724                .filter_map(|group| match kx_group_by_name(group) {
1725                    Some(kx) => Some(kx),
1726                    None => {
1727                        debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1728                        None
1729                    }
1730                })
1731                .collect::<Vec<_>>()
1732        };
1733
1734        let provider = Arc::new(CryptoProvider {
1735            cipher_suites: ciphers,
1736            kx_groups,
1737            ..default_provider()
1738        });
1739
1740        let builder = RustlsServerConfig::builder_with_provider(provider.clone())
1741            .with_protocol_versions(&versions[..])
1742            .map_err(|err| ListenerError::BuildRustls(err.to_string()))?;
1743
1744        // mTLS: `client_auth` decodes to `ClientAuthMode` (NONE at field 0).
1745        // NONE keeps the historical `.with_no_client_auth()`; OPTIONAL and
1746        // REQUIRED install a WebPki client-certificate verifier built from the
1747        // listener's trusted CA bundle (and optional CRLs). OPTIONAL differs
1748        // only by allowing connections that present no certificate at all. The
1749        // verifier is built from the same `provider` as the server config so it
1750        // works in single-provider builds (`crypto-openssl`-only) and in
1751        // multi-provider builds (`--all-features`) where no process-default
1752        // provider is installed.
1753        let builder = match Self::client_cert_verifier(config, &provider)? {
1754            Some(verifier) => builder.with_client_cert_verifier(verifier),
1755            None => builder.with_no_client_auth(),
1756        };
1757
1758        let mut server_config = builder.with_cert_resolver(resolver);
1759        server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1760
1761        server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1762            DEFAULT_ALPN_PROTOCOLS
1763                .iter()
1764                .map(|p| p.as_bytes().to_vec())
1765                .collect()
1766        } else {
1767            config
1768                .alpn_protocols
1769                .iter()
1770                .map(|p| p.as_bytes().to_vec())
1771                .collect()
1772        };
1773
1774        Ok(server_config)
1775    }
1776
1777    /// Refuse two CRLs of one CA whose scopes overlap: webpki checks a
1778    /// certificate against the first configured CRL that is authoritative for
1779    /// it and never reads the others, so the order alone would decide, and an
1780    /// old CRL listed before its replacement would hide every revocation the
1781    /// replacement adds. A CRL without an `IssuingDistributionPoint` covers
1782    /// everything its CA issued; partitions of one CA, each with its own
1783    /// distribution point, do not overlap and are accepted together.
1784    ///
1785    /// Issuers and distribution points are compared as the raw DER webpki
1786    /// compares. The check does not depend on time, so every worker runs it
1787    /// when it builds the listener; [`Self::check_crls_current`] is the one
1788    /// that does.
1789    fn check_crl_set(crls: &[CertificateRevocationListDer<'_>]) -> Result<(), ListenerError> {
1790        let mut scopes: Vec<(Vec<u8>, Option<Vec<u8>>)> = Vec::with_capacity(crls.len());
1791        for (index, crl) in crls.iter().enumerate() {
1792            let parsed = webpki::BorrowedCertRevocationList::from_der(crl.as_ref())
1793                .map_err(|e| ListenerError::ClientAuth(format!("invalid CRL: {e}")))?;
1794            let parsed = webpki::CertRevocationList::from(parsed);
1795            let issuer = parsed.issuer().to_vec();
1796            let distribution_point = parsed.issuing_distribution_point().map(<[u8]>::to_vec);
1797            let overlapping = scopes.iter().position(|(seen_issuer, seen_point)| {
1798                *seen_issuer == issuer
1799                    && (seen_point.is_none()
1800                        || distribution_point.is_none()
1801                        || *seen_point == distribution_point)
1802            });
1803            if let Some(first) = overlapping {
1804                return Err(ListenerError::ClientAuth(format!(
1805                    "CRLs #{} and #{} (counted across client_ca_crls) are issued by the \
1806                     same CA for overlapping certificates, and only the first would be \
1807                     consulted: configure one CRL per issuer, or one per distribution point",
1808                    first + 1,
1809                    index + 1
1810                )));
1811            }
1812            scopes.push((issuer, distribution_point));
1813        }
1814        Ok(())
1815    }
1816
1817    /// Refuse a CRL already past its `nextUpdate` at `now` (seconds since the
1818    /// Unix epoch): with revocation expiry enforced it would reject every
1819    /// client it covers, which is better reported when the operator states it
1820    /// than found at the first handshake.
1821    ///
1822    /// Main process only, on what an operator states (configuration load,
1823    /// reload, a client request), never on a replay or in a worker: a CRL
1824    /// that lapses while the listener runs must not stop a worker started
1825    /// later from building the listener, or the reload that brings a current
1826    /// CRL would find nothing to patch there. Such a worker builds it and
1827    /// rejects the clients at handshake until that reload.
1828    pub fn check_crls_current(crls: &[String], now: i64) -> Result<(), ListenerError> {
1829        let mut index = 0;
1830        for pem in crls {
1831            for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1832                let crl =
1833                    crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?;
1834                index += 1;
1835                if let Some(next_update) = sozu_command::certificate::crl_next_update(crl.as_ref())
1836                    && next_update <= now
1837                {
1838                    return Err(ListenerError::ClientAuth(format!(
1839                        "CRL #{index} (counted across client_ca_crls) expired {} seconds ago: \
1840                         its nextUpdate is past, refresh it",
1841                        now - next_update
1842                    )));
1843                }
1844            }
1845        }
1846        Ok(())
1847    }
1848
1849    /// Check a client authentication policy the way a listener would build
1850    /// it, without a listener: the main process runs this on an
1851    /// `UpdateHttpsListenerConfig.client_auth_policy` patch before it commits
1852    /// the patch to its state, so a policy no worker could build is refused
1853    /// up front rather than fanned out.
1854    pub fn validate_client_auth_policy(policy: &ClientAuthPolicy) -> Result<(), ListenerError> {
1855        let mut config = HttpsListenerConfig::default();
1856        config.apply_client_auth_policy(policy);
1857        Self::client_cert_verifier(&config, &Arc::new(default_provider())).map(|_| ())
1858    }
1859
1860    /// Build the rustls client-certificate verifier for a listener's mTLS
1861    /// configuration, or `None` when client auth is disabled.
1862    ///
1863    /// Returns `None` only for [`ClientAuthMode::ClientAuthNone`] (and for an
1864    /// absent `client_auth` field, which decodes to that variant), preserving
1865    /// the historical `.with_no_client_auth()` path. For OPTIONAL/REQUIRED it
1866    /// parses `client_ca_certificates` into a root store and, when present,
1867    /// `client_ca_crls` into revocation lists. OPTIONAL additionally allows
1868    /// unauthenticated clients (no certificate presented); REQUIRED aborts the
1869    /// handshake in that case.
1870    ///
1871    /// The verifier is built with the caller's `provider` so it never relies on
1872    /// a process-default crypto provider (which may be absent or ambiguous). An
1873    /// unknown `client_auth` value is rejected rather than treated as NONE, so a
1874    /// malformed or future enum value can never silently disable client auth.
1875    fn client_cert_verifier(
1876        config: &HttpsListenerConfig,
1877        provider: &Arc<CryptoProvider>,
1878    ) -> Result<Option<Arc<dyn rustls::server::danger::ClientCertVerifier>>, ListenerError> {
1879        // Reject unknown enum values instead of failing open to NONE.
1880        let raw = config
1881            .client_auth
1882            .unwrap_or(ClientAuthMode::ClientAuthNone as i32);
1883        let mode = ClientAuthMode::try_from(raw).map_err(|_| {
1884            ListenerError::ClientAuth(format!("unknown client_auth mode value {raw}"))
1885        })?;
1886        if mode == ClientAuthMode::ClientAuthNone {
1887            return Ok(None);
1888        }
1889
1890        // Parse every trusted-CA PEM entry into the root store. Each entry may
1891        // itself hold a concatenated PEM chain, so iterate per entry. An entry
1892        // that contributes no certificate (empty text, or PEM carrying another
1893        // section type) is rejected rather than skipped: it would start the
1894        // listener with only a subset of the configured trust anchors, so
1895        // clients issued by the omitted CA would fail with no visible cause.
1896        let mut roots = RootCertStore::empty();
1897        for pem in &config.client_ca_certificates {
1898            let before = roots.len();
1899            for cert in CertificateDer::pem_slice_iter(pem.as_bytes()) {
1900                let cert =
1901                    cert.map_err(|e| ListenerError::ClientAuth(format!("invalid CA PEM: {e}")))?;
1902                roots
1903                    .add(cert)
1904                    .map_err(|e| ListenerError::ClientAuth(format!("untrusted CA: {e}")))?;
1905            }
1906            if roots.len() == before {
1907                return Err(ListenerError::ClientAuth(
1908                    "a configured trusted-CA entry contained no certificate".to_owned(),
1909                ));
1910            }
1911        }
1912        if roots.is_empty() {
1913            return Err(ListenerError::ClientAuth(
1914                "client auth requested but no trusted CA certificate was provided".to_owned(),
1915            ));
1916        }
1917
1918        // Parse CRLs per configured entry. An entry that yields zero CRLs (empty
1919        // text, or PEM carrying another section type) is a misconfiguration: it
1920        // would silently disable revocation, so reject it rather than build a
1921        // verifier that skips `with_crls`.
1922        let mut crls = Vec::new();
1923        for pem in &config.client_ca_crls {
1924            let before = crls.len();
1925            for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1926                crls.push(
1927                    crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?,
1928                );
1929            }
1930            if crls.len() == before {
1931                return Err(ListenerError::ClientAuth(
1932                    "a configured CRL entry contained no certificate revocation list".to_owned(),
1933                ));
1934            }
1935        }
1936        Self::check_crl_set(&crls)?;
1937
1938        let mut builder =
1939            WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone());
1940        if !crls.is_empty() {
1941            // rustls defaults to `ExpirationPolicy::Ignore`, which keeps trusting
1942            // a CRL past its `nextUpdate`. Enforce expiration so a stale CRL is
1943            // rejected rather than silently accepting certificates whose
1944            // revocation status is no longer known.
1945            builder = builder.with_crls(crls).enforce_revocation_expiration();
1946        }
1947        if mode == ClientAuthMode::ClientAuthOptional {
1948            builder = builder.allow_unauthenticated();
1949        }
1950        let verifier = builder
1951            .build()
1952            .map_err(|e| ListenerError::ClientAuth(e.to_string()))?;
1953        Ok(Some(verifier))
1954    }
1955
1956    /// Apply a partial-update patch to this listener's live configuration.
1957    ///
1958    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
1959    /// If `alpn_protocols` is present the rustls `ServerConfig` is rebuilt —
1960    /// in-flight handshakes keep the old Arc; new ones see the new one.
1961    /// If `http_answers` is present only the listener-default templates are
1962    /// replaced; per-cluster overrides in `cluster_custom_answers` are kept.
1963    ///
1964    /// All-or-nothing (sozu-proxy/sozu#1703): the patch is validated, then
1965    /// applied to a copy of the configuration, and every fallible artifact
1966    /// (the rustls context, the answer registry) is built from that copy.
1967    /// Only once all of it succeeded are the copy and the artifacts
1968    /// committed and the inheriting frontends' HSTS refreshed, so a refused
1969    /// patch leaves the listener exactly as it was. The copy is one
1970    /// `HttpsListenerConfig` clone per patch, on the command path only.
1971    pub fn update_config(
1972        &mut self,
1973        patch: &UpdateHttpsListenerConfig,
1974    ) -> Result<(), ListenerError> {
1975        // Defense-in-depth validation: main-process ConfigState::dispatch
1976        // validates before scatter, but a raw protobuf client or state replay
1977        // may reach the worker without that check. `StateError` lifts into
1978        // `ListenerError` via `From` so `?` suffices. The main process runs
1979        // the same checks (`ConfigState::update_https_listener` and, for the
1980        // templates, `validate_listener_request` in
1981        // `bin/src/command/requests.rs`), so both refuse the same patches.
1982        validate_h2_flood_knobs_https(patch)?;
1983        if let Some(ref alpn) = patch.alpn_protocols {
1984            validate_alpn_protocols(&alpn.values)?;
1985        }
1986        if let Some(ref hdr) = patch.sozu_id_header {
1987            validate_sozu_id_header(hdr)?;
1988        }
1989        if let Some(v) = patch.forwarded_headers {
1990            validate_forwarded_headers(v)?;
1991        }
1992        // HSTS is a full-object replacement, and `enabled` is the explicit
1993        // disambiguator between "disable" and "enable" semantics: the
1994        // operator must signal one or the other on every update, so a block
1995        // without it is refused. The main process refuses it too, with
1996        // `StateError::InvalidValue` (`validate_hsts_patch`,
1997        // `command/src/state.rs`).
1998        if patch.hsts.is_some_and(|hsts| hsts.enabled.is_none()) {
1999            return Err(ListenerError::HstsEnabledRequired);
2000        }
2001        HttpAnswers::validate_patch_templates(patch.http_answers.as_ref(), &patch.answers)
2002            .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
2003
2004        let mut config = self.config.clone();
2005
2006        // --- simple field patches ---
2007        if let Some(v) = patch.public_address {
2008            config.public_address = Some(v);
2009        }
2010        if let Some(v) = patch.expect_proxy {
2011            config.expect_proxy = v;
2012        }
2013        if let Some(ref v) = patch.sticky_name {
2014            config.sticky_name = v.to_owned();
2015        }
2016        if let Some(v) = patch.front_timeout {
2017            config.front_timeout = v;
2018        }
2019        if let Some(v) = patch.back_timeout {
2020            config.back_timeout = v;
2021        }
2022        if let Some(v) = patch.connect_timeout {
2023            config.connect_timeout = v;
2024        }
2025        if let Some(v) = patch.request_timeout {
2026            config.request_timeout = v;
2027        }
2028        if let Some(v) = patch.strict_sni_binding {
2029            config.strict_sni_binding = Some(v);
2030        }
2031        if let Some(v) = patch.disable_http11 {
2032            config.disable_http11 = Some(v);
2033        }
2034        if let Some(ref v) = patch.sozu_id_header {
2035            config.sozu_id_header = Some(v.to_owned());
2036        }
2037        if let Some(v) = patch.elide_x_real_ip {
2038            config.elide_x_real_ip = Some(v);
2039        }
2040        if let Some(v) = patch.send_x_real_ip {
2041            config.send_x_real_ip = Some(v);
2042        }
2043        if let Some(v) = patch.forwarded_headers {
2044            config.forwarded_headers = Some(v);
2045        }
2046
2047        // --- H2 flood knobs ---
2048        if let Some(v) = patch.h2_max_rst_stream_per_window {
2049            config.h2_max_rst_stream_per_window = Some(v);
2050        }
2051        if let Some(v) = patch.h2_max_ping_per_window {
2052            config.h2_max_ping_per_window = Some(v);
2053        }
2054        if let Some(v) = patch.h2_max_settings_per_window {
2055            config.h2_max_settings_per_window = Some(v);
2056        }
2057        if let Some(v) = patch.h2_max_empty_data_per_window {
2058            config.h2_max_empty_data_per_window = Some(v);
2059        }
2060        if let Some(v) = patch.h2_max_continuation_frames {
2061            config.h2_max_continuation_frames = Some(v);
2062        }
2063        if let Some(v) = patch.h2_max_glitch_count {
2064            config.h2_max_glitch_count = Some(v);
2065        }
2066        if let Some(v) = patch.h2_initial_connection_window {
2067            config.h2_initial_connection_window = Some(v);
2068        }
2069        if let Some(v) = patch.h2_max_concurrent_streams {
2070            config.h2_max_concurrent_streams = Some(v);
2071        }
2072        if let Some(v) = patch.h2_stream_shrink_ratio {
2073            config.h2_stream_shrink_ratio = Some(v);
2074        }
2075        if let Some(v) = patch.h2_max_rst_stream_lifetime {
2076            config.h2_max_rst_stream_lifetime = Some(v);
2077        }
2078        if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
2079            config.h2_max_rst_stream_abusive_lifetime = Some(v);
2080        }
2081        if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
2082            config.h2_max_rst_stream_emitted_lifetime = Some(v);
2083        }
2084        if let Some(v) = patch.h2_max_header_list_size {
2085            config.h2_max_header_list_size = Some(v);
2086        }
2087        if let Some(v) = patch.h2_max_header_table_size {
2088            config.h2_max_header_table_size = Some(v);
2089        }
2090        if let Some(v) = patch.h2_max_header_fields {
2091            config.h2_max_header_fields = Some(v);
2092        }
2093        if let Some(v) = patch.h2_stream_refusal_percent {
2094            config.h2_stream_refusal_percent = Some(v);
2095        }
2096        if let Some(v) = patch.h2_stream_idle_timeout_seconds {
2097            config.h2_stream_idle_timeout_seconds = Some(v);
2098        }
2099        if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
2100            config.h2_graceful_shutdown_deadline_seconds = Some(v);
2101        }
2102        if let Some(v) = patch.h2_max_window_update_stream0_per_window {
2103            config.h2_max_window_update_stream0_per_window = Some(v);
2104        }
2105
2106        if let Some(new_hsts) = patch.hsts {
2107            config.hsts = Some(new_hsts);
2108        }
2109        if let Some(ref alpn_wrapper) = patch.alpn_protocols {
2110            // Empty values vec = reset to default (runtime treats empty as default)
2111            config.alpn_protocols = alpn_wrapper.values.clone();
2112        }
2113        if let Some(ref policy) = patch.client_auth_policy {
2114            config.apply_client_auth_policy(policy);
2115        }
2116
2117        // --- ALPN rebuild (may force a rustls ServerConfig rebuild) ---
2118        //
2119        // Built from the candidate config, which carries the new ALPN, and
2120        // published below only once every other fallible step succeeded, so
2121        // a rustls failure (crypto provider transient, resolver error, etc.)
2122        // leaves the listener observably unchanged. The main-process state
2123        // would still diverge from that worker-side refusal: the main process
2124        // cannot build this worker's rustls context before it commits.
2125        // A client authentication policy lives in the rustls context's client
2126        // certificate verifier, so a new policy needs the same rebuild; an
2127        // invalid one fails it here and the listener keeps its old policy.
2128        let new_rustls = if patch.alpn_protocols.is_some() || patch.client_auth_policy.is_some() {
2129            Some(Arc::new(Self::create_rustls_context(
2130                &config,
2131                self.resolver.clone(),
2132            )?))
2133        } else {
2134            None
2135        };
2136
2137        // HTTP answers: merge legacy `http_answers` and the new `answers`
2138        // map on top of the candidate config, then build the listener-level
2139        // template registry from it. It is fallible, so it is built here and
2140        // published below with the config, never before.
2141        let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
2142        let rebuilt = if answers_changed {
2143            if let Some(ref new_answers) = patch.http_answers {
2144                crate::sozu_command::state::merge_custom_http_answers(
2145                    &mut config.http_answers,
2146                    new_answers,
2147                );
2148            }
2149            for (code, body) in &patch.answers {
2150                if !body.is_empty() {
2151                    config.answers.insert(code.clone(), body.clone());
2152                }
2153            }
2154
2155            let mut answers_map = config.answers.clone();
2156            if let Some(ref legacy) = config.http_answers {
2157                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
2158            }
2159            Some(
2160                HttpAnswers::new(&answers_map)
2161                    .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?,
2162            )
2163        } else {
2164            None
2165        };
2166
2167        // Commit: nothing below can fail.
2168        self.config = config;
2169        if let Some(new_rustls) = new_rustls {
2170            self.rustls_details = new_rustls;
2171        }
2172        if let Some(alpn_wrapper) = &patch.alpn_protocols {
2173            // Post: the live config must now name exactly the patched ALPN
2174            // set. New handshakes negotiate against this set, so the
2175            // `upgrade_handshake` "protocol ∈ configured ALPN" property is
2176            // anchored to what we just stored.
2177            debug_assert_eq!(
2178                self.config.alpn_protocols, alpn_wrapper.values,
2179                "committed ALPN config must match the patch values exactly"
2180            );
2181        }
2182        if let Some(mut rebuilt) = rebuilt {
2183            // The rebuilt registry is PUBLISHED under a new `Rc`, not written
2184            // through the old one. Every request already in flight captured
2185            // the old handle when it arrived (`mux::Context::create_stream`)
2186            // and renders its default answers from that, so a reload landing
2187            // mid-request cannot change the page that request is about to
2188            // emit; requests arriving after this point capture the new handle.
2189            // That is the one-request staleness window of
2190            // `lib/src/protocol/mux/LIFECYCLE.md` §2.5.
2191            //
2192            // The per-cluster overrides are therefore COPIED, not
2193            // `mem::take`n: taking them would empty the registry those
2194            // in-flight requests still hold and strip their cluster templates
2195            // mid-response. The copy is a map clone over `Rc<Template>`, so
2196            // both registries share the compiled templates themselves.
2197            rebuilt.cluster_answers = self.answers.borrow().cluster_answers.clone();
2198            self.answers = Rc::new(RefCell::new(rebuilt));
2199        }
2200
2201        // HSTS: full-object replacement when present in the patch (stored
2202        // into the candidate config above; a block without `enabled` was
2203        // refused before the first write). Absent patch field preserves
2204        // current value (matches the rest of this partial-update handler).
2205        //
2206        // Inheriting frontends are refreshed in place via
2207        // `Router::refresh_inheriting_hsts`: every frontend that declared no
2208        // `hsts` of its own at add time
2209        // (`Frontend.inherits_listener_hsts == true`) gets its
2210        // `headers_response` re-materialised against the new value.
2211        // Explicit per-frontend overrides
2212        // (`inherits_listener_hsts == false`) are untouched. The
2213        // `http.hsts.listener_default_patched` counter still fires so
2214        // dashboards can correlate patches with the new
2215        // `http.hsts.frontend_refreshed` counter (sum of refreshed
2216        // frontends from this patch).
2217        if patch.hsts.is_some() {
2218            let refreshed = self
2219                .fronts
2220                .refresh_inheriting_hsts(self.config.hsts.as_ref());
2221            for _ in 0..refreshed {
2222                crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
2223            }
2224            info!(
2225                "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
2226                 frontend(s). Explicit per-frontend overrides untouched.",
2227                log_module_context!(),
2228                self.config.address,
2229                refreshed,
2230            );
2231            crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
2232        }
2233
2234        Ok(())
2235    }
2236
2237    pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2238        self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
2239    }
2240
2241    /// Variant of [`Self::add_https_front`] that records the origin of
2242    /// `tls_front.hsts` so listener-default patches can reflow inheriting
2243    /// frontends without disturbing explicit per-frontend overrides. The
2244    /// caller passes [`crate::router::HstsOrigin::InheritedFromListenerDefault`] when
2245    /// the value was filled in from `self.config.hsts` rather than from
2246    /// the operator's per-frontend configuration.
2247    pub fn add_https_front_with_hsts_origin(
2248        &mut self,
2249        tls_front: HttpFrontend,
2250        hsts_origin: crate::router::HstsOrigin,
2251    ) -> Result<(), ListenerError> {
2252        self.fronts
2253            .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
2254            .map_err(ListenerError::AddFrontend)
2255    }
2256
2257    pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2258        debug!(
2259            "{} removing tls_front {:?}",
2260            log_module_context!(),
2261            tls_front
2262        );
2263        self.fronts
2264            .remove_http_front(&tls_front)
2265            .map_err(ListenerError::RemoveFrontend)
2266    }
2267
2268    fn accept(&mut self) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2269        if let Some(ref sock) = self.listener {
2270            match sock.accept() {
2271                Ok((frontend_sock, peer)) => {
2272                    if self.nodelay_backlog
2273                        && let Err(e) = frontend_sock.set_nodelay(true)
2274                    {
2275                        error!(
2276                            "{} error setting nodelay on front socket({:?}): {:?}",
2277                            log_module_context!(),
2278                            frontend_sock,
2279                            e
2280                        );
2281                    }
2282                    Ok((frontend_sock, peer))
2283                }
2284                Err(e) if e.kind() == ErrorKind::WouldBlock => {
2285                    // The backlog queued before `activate()` is drained.
2286                    self.nodelay_backlog = false;
2287                    Err(AcceptError::WouldBlock)
2288                }
2289                Err(e) => {
2290                    error!("{} accept() IO error: {:?}", log_module_context!(), e);
2291                    Err(AcceptError::IoError)
2292                }
2293            }
2294        } else {
2295            error!(
2296                "{} cannot accept connections, no listening socket available",
2297                log_module_context!()
2298            );
2299            Err(AcceptError::IoError)
2300        }
2301    }
2302}
2303
2304pub struct HttpsProxy {
2305    listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
2306    clusters: HashMap<ClusterId, Cluster>,
2307    backends: Rc<RefCell<BackendMap>>,
2308    pool: Rc<RefCell<Pool>>,
2309    registry: Registry,
2310    sessions: Rc<RefCell<SessionManager>>,
2311}
2312
2313impl HttpsProxy {
2314    pub fn new(
2315        registry: Registry,
2316        sessions: Rc<RefCell<SessionManager>>,
2317        pool: Rc<RefCell<Pool>>,
2318        backends: Rc<RefCell<BackendMap>>,
2319    ) -> HttpsProxy {
2320        HttpsProxy {
2321            listeners: HashMap::new(),
2322            clusters: HashMap::new(),
2323            backends,
2324            pool,
2325            registry,
2326            sessions,
2327        }
2328    }
2329
2330    pub fn add_listener(
2331        &mut self,
2332        config: HttpsListenerConfig,
2333        token: Token,
2334    ) -> Result<Token, ProxyError> {
2335        if self.listeners.contains_key(&token) {
2336            return Err(ProxyError::ListenerAlreadyPresent);
2337        }
2338        let mut https_listener =
2339            HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
2340        // Frontends and certificates name their listener by address alone, so
2341        // a listener added next to one already on its address (on another
2342        // interface) takes over the routes and certificates that one serves:
2343        // every listener on an address holds the same ones, whenever it was
2344        // added (sozu-proxy/sozu#719). The certificates are copied into this
2345        // listener's own resolver, the one its rustls configuration already
2346        // points at, so later changes keep reaching it through the fan-out.
2347        if let Some(sibling) = self
2348            .listeners_at(https_listener.address)
2349            .ok()
2350            .and_then(|listeners| listeners.into_iter().next())
2351        {
2352            let sibling = sibling.borrow();
2353            https_listener.fronts = sibling.fronts.clone();
2354            // Frontends that declared no `hsts` of their own inherit this
2355            // listener's default instead of the sibling's, whether or not the
2356            // sibling had one (sozu-proxy/sozu#1691).
2357            https_listener
2358                .fronts
2359                .refresh_inheriting_hsts(https_listener.config.hsts.as_ref());
2360            https_listener.tags = sibling.tags.clone();
2361            let certificates = sibling
2362                .resolver
2363                .0
2364                .lock()
2365                .map_err(|e| ProxyError::Lock(e.to_string()))?
2366                .clone();
2367            *https_listener
2368                .resolver
2369                .0
2370                .lock()
2371                .map_err(|e| ProxyError::Lock(e.to_string()))? = certificates;
2372        }
2373        match self.listeners.entry(token) {
2374            Entry::Vacant(entry) => {
2375                entry.insert(Rc::new(RefCell::new(https_listener)));
2376                Ok(token)
2377            }
2378            _ => Err(ProxyError::ListenerAlreadyPresent),
2379        }
2380    }
2381
2382    pub fn remove_listener(
2383        &mut self,
2384        remove: RemoveListener,
2385    ) -> Result<Option<ResponseContent>, ProxyError> {
2386        let len = self.listeners.len();
2387
2388        let remove_address = remove.listener_key();
2389        self.listeners
2390            .retain(|_, listener| !listener.borrow().is(&remove_address));
2391
2392        if !self.listeners.len() < len {
2393            info!(
2394                "{} no HTTPS listener to remove at address {}",
2395                log_module_context!(),
2396                remove_address
2397            )
2398        }
2399        Ok(None)
2400    }
2401
2402    pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
2403        let listeners = std::mem::take(&mut self.listeners);
2404        let mut socket_errors = vec![];
2405        for l in listeners.values() {
2406            if let Some(mut sock) = l.borrow_mut().listener.take() {
2407                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2408                if let Err(e) = self.registry.deregister(&mut sock) {
2409                    let error = format!("socket {sock:?}: {e:?}");
2410                    socket_errors.push(error);
2411                }
2412            }
2413        }
2414
2415        if !socket_errors.is_empty() {
2416            return Err(ProxyError::SoftStop {
2417                proxy_protocol: "HTTPS".to_string(),
2418                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2419            });
2420        }
2421
2422        Ok(())
2423    }
2424
2425    pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
2426        let mut listeners = std::mem::take(&mut self.listeners);
2427        let mut socket_errors = vec![];
2428        for (_, l) in listeners.drain() {
2429            if let Some(mut sock) = l.borrow_mut().listener.take() {
2430                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2431                if let Err(e) = self.registry.deregister(&mut sock) {
2432                    let error = format!("socket {sock:?}: {e:?}");
2433                    socket_errors.push(error);
2434                }
2435            }
2436        }
2437
2438        if !socket_errors.is_empty() {
2439            return Err(ProxyError::HardStop {
2440                proxy_protocol: "HTTPS".to_string(),
2441                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2442            });
2443        }
2444
2445        Ok(())
2446    }
2447
2448    pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
2449        let certificates = self
2450            .listeners
2451            .values()
2452            .map(|listener| {
2453                let owned = listener.borrow();
2454                // Same handling as the `add`/`remove`/`replace` siblings: a
2455                // poisoned resolver is reported as `ProxyError::Lock`, never
2456                // unwrapped. A read-only query must not be the one control-
2457                // plane request that kills the worker.
2458                let resolver = owned
2459                    .resolver
2460                    .0
2461                    .lock()
2462                    .map_err(|e| ProxyError::Lock(e.to_string()))?;
2463                let certificate_summaries = resolver
2464                    .domains
2465                    .to_hashmap()
2466                    .drain()
2467                    .map(|(k, fingerprint)| CertificateSummary {
2468                        domain: certificate_summary_domain(&k),
2469                        fingerprint: fingerprint.to_string(),
2470                    })
2471                    .collect();
2472
2473                Ok(CertificatesByAddress {
2474                    address: owned.address.into(),
2475                    certificate_summaries,
2476                })
2477            })
2478            .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2479
2480        let listeners_count = certificates.len();
2481        let certificates_count = certificates
2482            .iter()
2483            .map(|entry| entry.certificate_summaries.len())
2484            .fold(0usize, |total, count| total.saturating_add(count));
2485
2486        info!(
2487            "{} got Certificates::All query, listeners_count={} certificates_count={}",
2488            log_module_context!(),
2489            listeners_count,
2490            certificates_count,
2491        );
2492
2493        Ok(Some(
2494            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2495        ))
2496    }
2497
2498    pub fn query_certificate_for_domain(
2499        &mut self,
2500        domain: String,
2501    ) -> Result<Option<ResponseContent>, ProxyError> {
2502        // The resolver keys its SNI trie on ASCII-lowercased certificate
2503        // names (`CertifiedKeyWrapper::try_from`), because that is the
2504        // only form rustls ever asks it for. An operator query has to
2505        // ask the same question a handshake does, or
2506        // `sozu query certificates --domain Example.COM` reports the
2507        // certificate missing while the proxy happily serves it.
2508        let domain_key = domain.to_ascii_lowercase();
2509        let certificates = self
2510            .listeners
2511            .values()
2512            .map(|listener| {
2513                let owned = listener.borrow();
2514                // See `query_all_certificates`: lock like the
2515                // `add`/`remove`/`replace` siblings do.
2516                let resolver = owned
2517                    .resolver
2518                    .0
2519                    .lock()
2520                    .map_err(|e| ProxyError::Lock(e.to_string()))?;
2521                let mut certificate_summaries = vec![];
2522
2523                if let Some((k, fingerprint)) = resolver.domain_lookup(domain_key.as_bytes(), true)
2524                {
2525                    certificate_summaries.push(CertificateSummary {
2526                        domain: certificate_summary_domain(k),
2527                        fingerprint: fingerprint.to_string(),
2528                    });
2529                }
2530                Ok(CertificatesByAddress {
2531                    address: owned.address.into(),
2532                    certificate_summaries,
2533                })
2534            })
2535            .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2536
2537        let listeners_count = certificates.len();
2538        let certificates_count = certificates
2539            .iter()
2540            .map(|entry| entry.certificate_summaries.len())
2541            .fold(0usize, |total, count| total.saturating_add(count));
2542
2543        info!(
2544            "{} got Certificates::Domain query, domain_bytes={} listeners_count={} certificates_count={}",
2545            log_module_context!(),
2546            domain.len(),
2547            listeners_count,
2548            certificates_count,
2549        );
2550
2551        Ok(Some(
2552            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2553        ))
2554    }
2555
2556    /// What this proxy would do with a socket handed to
2557    /// [`activate_listener`](Self::activate_listener) for `addr`. See
2558    /// [`crate::InheritedSocketFate`].
2559    ///
2560    /// The event loop is single-threaded, so nothing can change the listener's
2561    /// `active` flag between this answer and the `activate_listener` call that
2562    /// acts on it.
2563    pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2564        use crate::InheritedSocketFate;
2565        match self
2566            .listeners
2567            .values()
2568            .find(|listener| listener.borrow().is(addr))
2569        {
2570            None => InheritedSocketFate::Unclaimed,
2571            Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2572            Some(_) => InheritedSocketFate::Adopted,
2573        }
2574    }
2575
2576    pub fn activate_listener(
2577        &mut self,
2578        addr: &ListenerKey,
2579        tcp_listener: Option<MioTcpListener>,
2580    ) -> Result<Token, ProxyError> {
2581        let listener = self
2582            .listeners
2583            .values()
2584            .find(|listener| listener.borrow().is(addr))
2585            .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2586
2587        listener
2588            .borrow_mut()
2589            .activate(&self.registry, tcp_listener)
2590            .map_err(|listener_error| ProxyError::ListenerActivation {
2591                address: addr.address,
2592                listener_error,
2593            })
2594    }
2595
2596    /// The slab token reserved for the listener at `address`, if this proxy
2597    /// holds one.
2598    ///
2599    /// A listener owns exactly one slab slot for its whole `AddListener` ->
2600    /// `RemoveListener` lifetime, and no `remove_listener` implementation
2601    /// touches the session slab. `Server` therefore reads the token here
2602    /// BEFORE dropping the listener, so that reserved slot is released exactly
2603    /// once, at the end of the lifetime.
2604    pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
2605        self.listeners
2606            .iter()
2607            .find(|(_, listener)| listener.borrow().is(address))
2608            .map(|(token, _)| *token)
2609    }
2610
2611    pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
2612        self.listeners
2613            .values()
2614            .filter_map(|listener| {
2615                let mut owned = listener.borrow_mut();
2616                if let Some(listener) = owned.listener.take() {
2617                    // Reset `active` so a subsequent `activate()` re-binds
2618                    // instead of short-circuiting on the stale flag.
2619                    owned.active = false;
2620                    return Some((owned.key(), listener));
2621                }
2622
2623                None
2624            })
2625            .collect()
2626    }
2627
2628    pub fn give_back_listener(
2629        &mut self,
2630        address: &ListenerKey,
2631    ) -> Result<(Token, MioTcpListener), ProxyError> {
2632        let listener = self
2633            .listeners
2634            .values()
2635            .find(|listener| listener.borrow().is(address))
2636            .ok_or(ProxyError::NoListenerFound(address.clone()))?;
2637
2638        let mut owned = listener.borrow_mut();
2639
2640        let taken_listener = owned
2641            .listener
2642            .take()
2643            .ok_or(ProxyError::UnactivatedListener)?;
2644
2645        // Reset `active` so a subsequent `activate()` re-binds instead of
2646        // short-circuiting on the stale flag.
2647        owned.active = false;
2648
2649        Ok((owned.token, taken_listener))
2650    }
2651
2652    /// Every listener on `address`, whatever its interface. Frontends and
2653    /// certificates name their listener by address alone, so they apply to
2654    /// all the listeners sharing that address on different interfaces.
2655    fn listeners_at(
2656        &self,
2657        address: StdSocketAddr,
2658    ) -> Result<Vec<Rc<RefCell<HttpsListener>>>, ProxyError> {
2659        let mut listeners: Vec<_> = self
2660            .listeners
2661            .iter()
2662            .filter(|(_, listener)| listener.borrow().address == address)
2663            .map(|(token, listener)| (*token, listener.clone()))
2664            .collect();
2665        // Token order, so a failure is reported the same way on every run.
2666        listeners.sort_by_key(|(token, _)| *token);
2667        let listeners: Vec<_> = listeners
2668            .into_iter()
2669            .map(|(_, listener)| listener)
2670            .collect();
2671        if listeners.is_empty() {
2672            return Err(ProxyError::NoListenerFound(address.into()));
2673        }
2674        Ok(listeners)
2675    }
2676
2677    /// Apply a partial-update patch to the identified HTTPS listener.
2678    pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
2679        let address: std::net::SocketAddr = patch.address.into();
2680        let listener = self
2681            .listeners
2682            .values()
2683            .find(|l| l.borrow().address == address)
2684            .ok_or(ProxyError::NoListenerFound(address.into()))?;
2685        listener
2686            .borrow_mut()
2687            .update_config(&patch)
2688            .map_err(|listener_error| ProxyError::ListenerActivation {
2689                address,
2690                listener_error,
2691            })
2692    }
2693
2694    pub fn add_cluster(
2695        &mut self,
2696        mut cluster: Cluster,
2697    ) -> Result<Option<ResponseContent>, ProxyError> {
2698        let mut cluster_overrides = cluster.answers.clone();
2699        if let Some(answer_503) = cluster.answer_503.take() {
2700            cluster_overrides
2701                .entry("503".to_owned())
2702                .or_insert(answer_503);
2703        }
2704        if !cluster_overrides.is_empty() {
2705            for listener in self.listeners.values() {
2706                listener
2707                    .borrow()
2708                    .answers
2709                    .borrow_mut()
2710                    .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
2711                    .map_err(|(status, error)| {
2712                        ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
2713                    })?;
2714            }
2715        }
2716        self.clusters
2717            .insert(cluster.cluster_id.as_str().into(), cluster);
2718        Ok(None)
2719    }
2720
2721    pub fn remove_cluster(
2722        &mut self,
2723        cluster_id: &str,
2724    ) -> Result<Option<ResponseContent>, ProxyError> {
2725        self.clusters.remove(cluster_id);
2726        for listener in self.listeners.values() {
2727            listener
2728                .borrow()
2729                .answers
2730                .borrow_mut()
2731                .remove_cluster_answers(cluster_id);
2732        }
2733
2734        Ok(None)
2735    }
2736
2737    pub fn add_https_frontend(
2738        &mut self,
2739        front: RequestHttpFrontend,
2740    ) -> Result<Option<ResponseContent>, ProxyError> {
2741        let front = front.clone().to_frontend().map_err(|request_error| {
2742            ProxyError::WrongInputFrontend {
2743                front: Box::new(front),
2744                error: request_error.to_string(),
2745            }
2746        })?;
2747
2748        for listener in self.listeners_at(front.address)? {
2749            let mut listener = listener.borrow_mut();
2750            let mut front = front.clone();
2751
2752            // ── HSTS listener-default → frontend inheritance ─────────────────
2753            // When the frontend declares no `hsts` block, fall back to the
2754            // listener default so the operator can opt into HSTS once at the
2755            // listener and have every HTTPS frontend inherit it.
2756            // `enabled = Some(false)` on the frontend is the explicit-disable
2757            // signal: it stays as-is and suppresses the inherited default.
2758            //
2759            // The `hsts_origin` flag records the frontend's own intent — it
2760            // declared no `hsts` — rather than whether a default existed, so
2761            // the resulting `Frontend` carries the inheritance bit even when
2762            // the listener has no default yet. A later
2763            // `UpdateHttpsListenerConfig.hsts` patch, or the copy into a
2764            // listener added on the same address (`HttpsProxy::add_listener`), then
2765            // resolves it against that listener's own default via
2766            // `Router::refresh_inheriting_hsts` without disturbing explicit
2767            // per-frontend overrides (sozu-proxy/sozu#1691).
2768            let hsts_origin = if front.hsts.is_none() {
2769                front.hsts = listener.config.hsts;
2770                crate::router::HstsOrigin::InheritedFromListenerDefault
2771            } else {
2772                crate::router::HstsOrigin::Explicit
2773            };
2774
2775            listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
2776            listener
2777                .add_https_front_with_hsts_origin(front, hsts_origin)
2778                .map_err(ProxyError::AddFrontend)?;
2779        }
2780        Ok(None)
2781    }
2782
2783    pub fn remove_https_frontend(
2784        &mut self,
2785        front: RequestHttpFrontend,
2786    ) -> Result<Option<ResponseContent>, ProxyError> {
2787        let front = front.clone().to_frontend().map_err(|request_error| {
2788            ProxyError::WrongInputFrontend {
2789                front: Box::new(front),
2790                error: request_error.to_string(),
2791            }
2792        })?;
2793
2794        // Every listener on the address drops the route, even when another
2795        // one fails: a sibling must never keep a route the others removed.
2796        // The first failure, in token order, is the one reported.
2797        let mut result = Ok(None);
2798        for listener in self.listeners_at(front.address)? {
2799            let mut listener = listener.borrow_mut();
2800            let hostname = front.hostname.to_owned();
2801
2802            if let Err(error) = listener.remove_https_front(front.clone())
2803                && result.is_ok()
2804            {
2805                result = Err(ProxyError::RemoveFrontend(error));
2806            }
2807
2808            if !listener.fronts.has_hostname(&hostname) {
2809                listener.set_tags(hostname, None);
2810            }
2811        }
2812        result
2813    }
2814
2815    pub fn add_certificate(
2816        &mut self,
2817        add_certificate: AddCertificate,
2818    ) -> Result<Option<ResponseContent>, ProxyError> {
2819        let address = add_certificate.address.into();
2820
2821        for listener in self.listeners_at(address)? {
2822            let listener = listener.borrow();
2823            let mut resolver = listener
2824                .resolver
2825                .0
2826                .lock()
2827                .map_err(|e| ProxyError::Lock(e.to_string()))?;
2828
2829            resolver
2830                .add_certificate(&add_certificate)
2831                .map_err(ProxyError::AddCertificate)?;
2832        }
2833
2834        Ok(None)
2835    }
2836
2837    //FIXME: should return an error if certificate still has fronts referencing it
2838    pub fn remove_certificate(
2839        &mut self,
2840        remove_certificate: RemoveCertificate,
2841    ) -> Result<Option<ResponseContent>, ProxyError> {
2842        let address = remove_certificate.address.into();
2843
2844        let fingerprint = Fingerprint(
2845            hex::decode(&remove_certificate.fingerprint)
2846                .map_err(ProxyError::WrongCertificateFingerprint)?,
2847        );
2848
2849        // Every listener on the address applies the change, even when another
2850        // one fails, so none keeps a certificate its siblings dropped; the
2851        // first failure, in token order, is the one reported.
2852        let mut result = Ok(None);
2853        for listener in self.listeners_at(address)? {
2854            let listener = listener.borrow();
2855            let applied = listener
2856                .resolver
2857                .0
2858                .lock()
2859                .map_err(|e| ProxyError::Lock(e.to_string()))
2860                .and_then(|mut resolver| {
2861                    resolver
2862                        .remove_certificate(&fingerprint)
2863                        .map_err(ProxyError::RemoveCertificate)
2864                });
2865            if let Err(error) = applied
2866                && result.is_ok()
2867            {
2868                result = Err(error);
2869            }
2870        }
2871        result
2872    }
2873
2874    //FIXME: should return an error if certificate still has fronts referencing it
2875    pub fn replace_certificate(
2876        &mut self,
2877        replace_certificate: ReplaceCertificate,
2878    ) -> Result<Option<ResponseContent>, ProxyError> {
2879        let address = replace_certificate.address.into();
2880
2881        // Every listener on the address applies the change, even when another
2882        // one fails, so none keeps a certificate its siblings dropped; the
2883        // first failure, in token order, is the one reported.
2884        let mut result = Ok(None);
2885        for listener in self.listeners_at(address)? {
2886            let listener = listener.borrow();
2887            let applied = listener
2888                .resolver
2889                .0
2890                .lock()
2891                .map_err(|e| ProxyError::Lock(e.to_string()))
2892                .and_then(|mut resolver| {
2893                    resolver
2894                        .replace_certificate(&replace_certificate)
2895                        .map_err(ProxyError::ReplaceCertificate)
2896                });
2897            if let Err(error) = applied
2898                && result.is_ok()
2899            {
2900                result = Err(error);
2901            }
2902        }
2903        result
2904    }
2905}
2906
2907impl ProxyConfiguration for HttpsProxy {
2908    fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2909        match self.listeners.get(&Token(token.0)) {
2910            Some(listener) => listener.borrow_mut().accept(),
2911            None => Err(AcceptError::IoError),
2912        }
2913    }
2914
2915    fn create_session(
2916        &mut self,
2917        mut frontend_sock: MioTcpStream,
2918        peer: StdSocketAddr,
2919        token: ListenToken,
2920        wait_time: Duration,
2921        proxy: Rc<RefCell<Self>>,
2922    ) -> Result<(), AcceptError> {
2923        let listener = self
2924            .listeners
2925            .get(&Token(token.0))
2926            .ok_or(AcceptError::IoError)?;
2927        // No `set_nodelay` here: the socket has `TCP_NODELAY` from its
2928        // listener, or from `HttpsListener::accept` if it was queued before
2929        // the listener set it.
2930
2931        let owned = listener.borrow();
2932        let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
2933            error!(
2934                "{} failed to create server session: {:?}",
2935                log_module_context!(),
2936                e
2937            );
2938            AcceptError::IoError
2939        })?;
2940
2941        let mut session_manager = self.sessions.borrow_mut();
2942        let entry = session_manager.slab.vacant_entry();
2943        let session_token = Token(entry.key());
2944        // The session token IS the slab key: the event loop later indexes the
2945        // slab directly by the mio `Token` it receives, so any divergence here
2946        // would route readiness to the wrong session slot. Snapshot the key to
2947        // re-check after `entry.insert` consumes the entry.
2948        debug_assert_eq!(
2949            session_token.0,
2950            entry.key(),
2951            "HTTPS session token must equal its slab key"
2952        );
2953
2954        self.registry
2955            .register(
2956                &mut frontend_sock,
2957                session_token,
2958                Interest::READABLE | Interest::WRITABLE,
2959            )
2960            .map_err(|register_error| {
2961                error!(
2962                    "{} error registering front socket({:?}): {:?}",
2963                    log_module_context!(),
2964                    frontend_sock,
2965                    register_error
2966                );
2967                AcceptError::RegisterError
2968            })?;
2969
2970        let public_address: StdSocketAddr = match owned.config.public_address {
2971            Some(pub_addr) => pub_addr.into(),
2972            None => owned.config.address.into(),
2973        };
2974
2975        let session = Rc::new(RefCell::new(HttpsSession::new(
2976            Duration::from_secs(owned.config.back_timeout as u64),
2977            Duration::from_secs(owned.config.connect_timeout as u64),
2978            Duration::from_secs(owned.config.front_timeout as u64),
2979            Duration::from_secs(owned.config.request_timeout as u64),
2980            owned.config.expect_proxy,
2981            listener.clone(),
2982            Rc::downgrade(&self.pool),
2983            proxy,
2984            public_address,
2985            rustls_details,
2986            frontend_sock,
2987            peer,
2988            session_token,
2989            wait_time,
2990        )));
2991        // The freshly built session must own exactly the token it is filed under,
2992        // so event-loop dispatch (slab key → session) and self-removal
2993        // (`frontend_token()` → slab key) agree.
2994        debug_assert_eq!(
2995            session.borrow().frontend_token(),
2996            session_token,
2997            "stored HTTPS session must report the slab token as its frontend token"
2998        );
2999        entry.insert(session);
3000
3001        Ok(())
3002    }
3003
3004    fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
3005        let request_id = request.id.clone();
3006
3007        let request_type = match request.content.request_type {
3008            Some(t) => t,
3009            None => return WorkerResponse::error(request_id, "Empty request"),
3010        };
3011
3012        let content_result = match request_type {
3013            RequestType::AddCluster(cluster) => {
3014                debug!(
3015                    "{} {} add cluster {:?}",
3016                    log_module_context!(),
3017                    request_id,
3018                    cluster
3019                );
3020                self.add_cluster(cluster)
3021            }
3022            RequestType::RemoveCluster(cluster_id) => {
3023                debug!(
3024                    "{} {} remove cluster {:?}",
3025                    log_module_context!(),
3026                    request_id,
3027                    cluster_id
3028                );
3029                self.remove_cluster(&cluster_id)
3030            }
3031            RequestType::AddHttpsFrontend(front) => {
3032                debug!(
3033                    "{} {} add https front {:?}",
3034                    log_module_context!(),
3035                    request_id,
3036                    front
3037                );
3038                self.add_https_frontend(front)
3039            }
3040            RequestType::RemoveHttpsFrontend(front) => {
3041                debug!(
3042                    "{} {} remove https front {:?}",
3043                    log_module_context!(),
3044                    request_id,
3045                    front
3046                );
3047                self.remove_https_frontend(front)
3048            }
3049            RequestType::AddCertificate(add_certificate) => {
3050                debug!(
3051                    "{} {} add certificate: {:?}",
3052                    log_module_context!(),
3053                    request_id,
3054                    add_certificate
3055                );
3056                self.add_certificate(add_certificate)
3057            }
3058            RequestType::RemoveCertificate(remove_certificate) => {
3059                debug!(
3060                    "{} {} remove certificate: {:?}",
3061                    log_module_context!(),
3062                    request_id,
3063                    remove_certificate
3064                );
3065                self.remove_certificate(remove_certificate)
3066            }
3067            RequestType::ReplaceCertificate(replace_certificate) => {
3068                debug!(
3069                    "{} {} replace certificate: {:?}",
3070                    log_module_context!(),
3071                    request_id,
3072                    replace_certificate
3073                );
3074                self.replace_certificate(replace_certificate)
3075            }
3076            RequestType::RemoveListener(remove) => {
3077                debug!(
3078                    "{} removing HTTPS listener at address {:?}",
3079                    log_module_context!(),
3080                    remove.address
3081                );
3082                self.remove_listener(remove)
3083            }
3084            RequestType::SoftStop(_) => {
3085                debug!(
3086                    "{} {} processing soft shutdown",
3087                    log_module_context!(),
3088                    request_id
3089                );
3090                match self.soft_stop() {
3091                    Ok(_) => {
3092                        info!(
3093                            "{} {} soft stop successful",
3094                            log_module_context!(),
3095                            request_id
3096                        );
3097                        return WorkerResponse::processing(request.id);
3098                    }
3099                    Err(e) => Err(e),
3100                }
3101            }
3102            RequestType::HardStop(_) => {
3103                debug!(
3104                    "{} {} processing hard shutdown",
3105                    log_module_context!(),
3106                    request_id
3107                );
3108                match self.hard_stop() {
3109                    Ok(_) => {
3110                        debug!(
3111                            "{} {} hard stop successful",
3112                            log_module_context!(),
3113                            request_id
3114                        );
3115                        return WorkerResponse::processing(request.id);
3116                    }
3117                    Err(e) => Err(e),
3118                }
3119            }
3120            RequestType::Status(_) => {
3121                debug!("{} {} status", log_module_context!(), request_id);
3122                Ok(None)
3123            }
3124            RequestType::QueryCertificatesFromWorkers(filters) => {
3125                if let Some(domain) = filters.domain {
3126                    debug!(
3127                        "{} {} query certificate for domain_bytes={}",
3128                        log_module_context!(),
3129                        request_id,
3130                        domain.len(),
3131                    );
3132                    self.query_certificate_for_domain(domain)
3133                } else {
3134                    debug!(
3135                        "{} {} query all certificates",
3136                        log_module_context!(),
3137                        request_id
3138                    );
3139                    self.query_all_certificates()
3140                }
3141            }
3142            other_request => {
3143                debug!(
3144                    "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
3145                    log_module_context!(),
3146                    request.id,
3147                    other_request
3148                );
3149                Err(ProxyError::UnsupportedMessage)
3150            }
3151        };
3152
3153        match content_result {
3154            Ok(content) => {
3155                debug!("{} {} successful", log_module_context!(), request_id);
3156                match content {
3157                    Some(content) => WorkerResponse::ok_with_content(request_id, content),
3158                    None => WorkerResponse::ok(request_id),
3159                }
3160            }
3161            Err(proxy_error) => {
3162                debug!(
3163                    "{} {} unsuccessful: {}",
3164                    log_module_context!(),
3165                    request_id,
3166                    proxy_error
3167                );
3168                WorkerResponse::error(request_id, proxy_error)
3169            }
3170        }
3171    }
3172}
3173impl L7Proxy for HttpsProxy {
3174    fn kind(&self) -> ListenerType {
3175        ListenerType::Https
3176    }
3177
3178    fn register_socket(
3179        &self,
3180        socket: &mut MioTcpStream,
3181        token: Token,
3182        interest: Interest,
3183    ) -> Result<(), std::io::Error> {
3184        self.registry.register(socket, token, interest)
3185    }
3186
3187    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
3188        let mut session_manager = self.sessions.borrow_mut();
3189        let entry = session_manager.slab.vacant_entry();
3190        let token = Token(entry.key());
3191        let _entry = entry.insert(session);
3192        token
3193    }
3194
3195    fn remove_session(&self, token: Token) -> bool {
3196        let mut sessions = self.sessions.borrow_mut();
3197        // Mirror of HttpProxy::remove_session — drain the per-(cluster,
3198        // source-IP) accounting before the slab slot is reused.
3199        sessions.untrack_all_cluster_ip(token);
3200        sessions.slab.try_remove(token.0).is_some()
3201    }
3202
3203    fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
3204        &self.clusters
3205    }
3206
3207    fn sessions(&self) -> Rc<RefCell<SessionManager>> {
3208        self.sessions.clone()
3209    }
3210}
3211
3212/// Used for metrics keeping
3213fn rustls_version_str(version: ProtocolVersion) -> &'static str {
3214    match version {
3215        ProtocolVersion::SSLv2 => "tls.version.SSLv2",
3216        ProtocolVersion::SSLv3 => "tls.version.SSLv3",
3217        ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
3218        ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
3219        ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
3220        ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
3221        ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
3222        ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
3223        ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
3224        ProtocolVersion::Unknown(_) => "tls.version.Unknown",
3225        _ => "tls.version.unimplemented",
3226    }
3227}
3228
3229/// Short label suitable for access logs (e.g. `"TLSv1.3"`).
3230///
3231/// Distinct from [`rustls_version_str`] which prefixes with `tls.version.`
3232/// for metric ingestion. Returns `None` for variants Sōzu does not know how
3233/// to label, so the access log records `tls_version` as absent rather than
3234/// emitting a misleading `"unimplemented"` literal.
3235pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
3236    match version {
3237        ProtocolVersion::SSLv2 => Some("SSLv2"),
3238        ProtocolVersion::SSLv3 => Some("SSLv3"),
3239        ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
3240        ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
3241        ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
3242        ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
3243        ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
3244        ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
3245        ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
3246        _ => None,
3247    }
3248}
3249
3250/// Used for metrics keeping
3251fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
3252    match cipher.suite() {
3253        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3254            "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
3255        }
3256        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3257            "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
3258        }
3259        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3260            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
3261        }
3262        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3263            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
3264        }
3265        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3266            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
3267        }
3268        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3269            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
3270        }
3271        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
3272        CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
3273        CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
3274        _ => "tls.cipher.Unsupported",
3275    }
3276}
3277
3278/// Short label suitable for access logs (e.g. `"TLS_AES_128_GCM_SHA256"`).
3279///
3280/// Distinct from [`rustls_ciphersuite_str`] which prefixes with `tls.cipher.`
3281/// for metric ingestion. Returns `None` for cipher suites Sōzu does not know
3282/// how to label, so the access log records `tls_cipher` as absent rather
3283/// than emitting a misleading `"Unsupported"` literal.
3284pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
3285    match cipher.suite() {
3286        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3287            Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
3288        }
3289        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3290            Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
3291        }
3292        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3293            Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
3294        }
3295        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3296            Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
3297        }
3298        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3299            Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
3300        }
3301        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3302            Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
3303        }
3304        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
3305        CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
3306        CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
3307        _ => None,
3308    }
3309}
3310
3311pub mod testing {
3312    use crate::testing::*;
3313
3314    /// this function is not used, but is available for example and testing purposes
3315    pub fn start_https_worker(
3316        config: HttpsListenerConfig,
3317        channel: ProxyChannel,
3318        max_buffers: usize,
3319        buffer_size: usize,
3320    ) -> anyhow::Result<()> {
3321        let address = config.address.into();
3322
3323        let ServerParts {
3324            event_loop,
3325            registry,
3326            sessions,
3327            pool,
3328            backends,
3329            client_scm_socket: _,
3330            server_scm_socket,
3331            server_config,
3332        } = prebuild_server(max_buffers, buffer_size, true)?;
3333
3334        let token = {
3335            let mut sessions = sessions.borrow_mut();
3336            let entry = sessions.slab.vacant_entry();
3337            let key = entry.key();
3338            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3339                protocol: Protocol::HTTPSListen,
3340            })));
3341            Token(key)
3342        };
3343
3344        let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3345        proxy
3346            .add_listener(config, token)
3347            .with_context(|| "Failed at creating adding the listener")?;
3348        proxy
3349            .activate_listener(&address, None)
3350            .with_context(|| "Failed at creating activating the listener")?;
3351
3352        let mut server = Server::new(
3353            event_loop,
3354            channel,
3355            server_scm_socket,
3356            sessions,
3357            pool,
3358            backends,
3359            None,
3360            Some(proxy),
3361            None,
3362            server_config,
3363            None,
3364            false,
3365        )
3366        .with_context(|| "Failed at creating server")?;
3367
3368        debug!("{} starting event loop", log_module_context!());
3369        server.run();
3370        debug!("{} ending event loop", log_module_context!());
3371        Ok(())
3372    }
3373}
3374
3375#[cfg(test)]
3376mod tests {
3377    use std::sync::Arc;
3378
3379    use sozu_command::{
3380        config::ListenerBuilder,
3381        proto::command::{CertificateAndKey, SocketAddress},
3382    };
3383
3384    use super::*;
3385    use crate::{
3386        router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode},
3387        socket::SocketHandler,
3388    };
3389
3390    /// A handshake whose last `recv` proved the socket empty hands over no
3391    /// READABLE: the upgraded mux must not `recv` for an EAGAIN (#1609).
3392    /// READABLE survives when the handshake still had it, or when rustls
3393    /// holds decrypted input; WRITABLE always does.
3394    #[test]
3395    fn the_upgraded_frontend_reads_only_when_a_read_is_due() {
3396        assert_eq!(
3397            upgraded_frontend_events(Ready::EMPTY, false),
3398            Ready::WRITABLE,
3399            "a drained socket and an empty rustls need no read"
3400        );
3401        assert_eq!(
3402            upgraded_frontend_events(Ready::WRITABLE, false),
3403            Ready::WRITABLE
3404        );
3405        assert_eq!(
3406            upgraded_frontend_events(Ready::READABLE, false),
3407            Ready::READABLE | Ready::WRITABLE,
3408            "an edge not yet read must survive the upgrade"
3409        );
3410        assert_eq!(
3411            upgraded_frontend_events(Ready::EMPTY, true),
3412            Ready::READABLE | Ready::WRITABLE,
3413            "plaintext rustls already holds raises no edge"
3414        );
3415        assert_eq!(
3416            upgraded_frontend_events(Ready::READABLE | Ready::HUP, true),
3417            Ready::READABLE | Ready::WRITABLE | Ready::HUP,
3418            "a half-close seen during the handshake must reach the mux"
3419        );
3420        assert_eq!(
3421            upgraded_frontend_events(Ready::HUP, false),
3422            Ready::WRITABLE | Ready::HUP,
3423            "HUP survives even when no read is due"
3424        );
3425        assert_eq!(
3426            upgraded_frontend_events(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED, true),
3427            Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::WRITE_CLOSED,
3428            "a hang-up seen during the handshake must reach the mux as one"
3429        );
3430    }
3431
3432    #[test]
3433    fn successful_tls_handshake_log_bounds_sni_and_alpn() {
3434        const SNI_SECRET: &str = "HTTPS_HANDSHAKE_SNI_SECRET_SENTINEL";
3435        const ALPN_SECRET: &str = "HTTPS_HANDSHAKE_ALPN_SECRET_SENTINEL";
3436
3437        let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
3438        let alpn = format!("{ALPN_SECRET}{}", "x".repeat(4096));
3439        let sni_len = sni.len();
3440        let alpn_len = alpn.len();
3441        let output = successful_tls_handshake_summary(Some(&sni), Some(&alpn));
3442
3443        for secret in [SNI_SECRET, ALPN_SECRET] {
3444            assert!(
3445                !output.contains(secret),
3446                "successful TLS handshake log leaked {secret}: {output}"
3447            );
3448        }
3449        for metadata in [
3450            format!("sni_bytes=Some({sni_len})"),
3451            format!("alpn_bytes=Some({alpn_len})"),
3452        ] {
3453            assert!(
3454                output.contains(&metadata),
3455                "successful TLS handshake log omitted {metadata}: {output}"
3456            );
3457        }
3458        assert!(
3459            output.len() <= 512,
3460            "successful TLS handshake log is not bounded: {} bytes",
3461            output.len()
3462        );
3463    }
3464
3465    fn proxy_with_certificate_domain(domain: String) -> HttpsProxy {
3466        let crate::testing::ServerParts {
3467            registry,
3468            sessions,
3469            pool,
3470            backends,
3471            ..
3472        } = crate::testing::prebuild_server(16, 16_384, false)
3473            .expect("test HTTPS proxy dependencies must initialize");
3474        let address = SocketAddress::new_v4(127, 0, 0, 1, crate::testing::provide_port());
3475        let mut config = ListenerBuilder::new_https(address)
3476            .to_tls(None)
3477            .expect("test HTTPS listener config must build");
3478        config.cipher_list = vec!["TLS13_AES_256_GCM_SHA384".to_owned()];
3479        config.groups_list = vec!["P-256".to_owned()];
3480        let mut proxy = HttpsProxy::new(registry, sessions, pool, backends);
3481        proxy
3482            .add_listener(config, Token(10))
3483            .expect("test HTTPS listener must be added");
3484        proxy
3485            .add_certificate(AddCertificate {
3486                address,
3487                certificate: CertificateAndKey {
3488                    certificate: include_str!("../assets/certificate.pem").to_owned(),
3489                    key: include_str!("../assets/key.pem").to_owned(),
3490                    certificate_chain: Vec::new(),
3491                    versions: Vec::new(),
3492                    names: vec![domain],
3493                },
3494                expired_at: None,
3495            })
3496            .expect("test certificate must be added");
3497        proxy
3498    }
3499
3500    /// The two `query_*` paths take the resolver mutex with `unwrap_msg!`,
3501    /// while every `add`/`remove`/`replace` sibling takes it with
3502    /// `.lock().map_err(|e| ProxyError::Lock(e.to_string()))?`
3503    /// (https.rs `add_certificate`/`remove_certificate`/`replace_certificate`).
3504    /// That asymmetry is the finding: a poisoned resolver — any earlier panic
3505    /// while the lock was held — turns a read-only `QueryCertificates`
3506    /// control-plane request into a worker kill instead of an error response.
3507    ///
3508    /// To SEE THIS RED: put `unwrap_msg!(owned.resolver.0.lock())` back in
3509    /// `query_all_certificates` and `query_certificate_for_domain` (restoring
3510    /// the `util::UnwrapLog` import the removal made unused) — both calls
3511    /// below then panic with `PoisonError` instead of returning
3512    /// `ProxyError::Lock`.
3513    #[test]
3514    fn a_poisoned_resolver_fails_the_certificate_queries_instead_of_the_worker() {
3515        let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3516
3517        let resolver = proxy
3518            .listeners
3519            .values()
3520            .next()
3521            .expect("the test proxy has one listener")
3522            .borrow()
3523            .resolver()
3524            .clone();
3525
3526        // Poison the mutex exactly as a panic inside the worker would: hold
3527        // the guard, unwind. `join` swallows the unwind so the test process
3528        // survives to observe the poisoned state.
3529        let poisoner = resolver.clone();
3530        let unwound = std::thread::spawn(move || {
3531            let _guard = poisoner.0.lock().expect("first lock is not poisoned");
3532            panic!("simulated worker panic while holding the resolver lock");
3533        })
3534        .join();
3535        assert!(unwound.is_err(), "the poisoning thread must have unwound");
3536        assert!(
3537            resolver.0.lock().is_err(),
3538            "the resolver mutex must now be poisoned"
3539        );
3540
3541        assert!(
3542            matches!(proxy.query_all_certificates(), Err(ProxyError::Lock(_))),
3543            "Certificates::All on a poisoned resolver must report a lock error"
3544        );
3545        assert!(
3546            matches!(
3547                proxy.query_certificate_for_domain("lolcatho.st".to_owned()),
3548                Err(ProxyError::Lock(_))
3549            ),
3550            "Certificates::Domain on a poisoned resolver must report a lock error"
3551        );
3552    }
3553
3554    /// `String::from_utf8(trie_key).unwrap()` in `query_all_certificates` is a
3555    /// second unconditional panic on a value the function does not own. The
3556    /// control plane cannot currently reach it — `AddCertificate.names` is
3557    /// `Vec<String>` and `tls.rs::add_certificate` inserts
3558    /// `String::into_bytes()`, while `TrieNode::to_hashmap` clones the stored
3559    /// key back verbatim — so this is an invariant, and the red below is BY
3560    /// MUTATION of the trie: `TrieNode<Fingerprint>` is `pub` and its key type
3561    /// is `Vec<u8>`, so the type permits what the control plane does not. A
3562    /// certificate listing must not be able to kill the worker over a key it
3563    /// merely echoes.
3564    ///
3565    /// To SEE THIS RED: put `String::from_utf8(k).unwrap()` back in
3566    /// `query_all_certificates` — this test then panics with
3567    /// `FromUtf8Error`.
3568    #[test]
3569    fn a_non_utf8_resolver_key_is_listed_lossily_not_panicked() {
3570        let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3571
3572        let resolver = proxy
3573            .listeners
3574            .values()
3575            .next()
3576            .expect("the test proxy has one listener")
3577            .borrow()
3578            .resolver()
3579            .clone();
3580        {
3581            let mut owned = resolver.0.lock().expect("resolver lock");
3582            let inserted = owned
3583                .domains
3584                .insert(vec![0xff, 0xfe], Fingerprint(vec![0x01, 0x02]));
3585            assert_ne!(
3586                inserted,
3587                crate::router::pattern_trie::InsertResult::Failed,
3588                "the SNI trie must accept the non-UTF-8 key this test relies on"
3589            );
3590        }
3591
3592        let response = proxy
3593            .query_all_certificates()
3594            .expect("a non-UTF-8 trie key must not fail the query");
3595        assert!(
3596            response.is_some(),
3597            "Certificates::All must still answer with a listing"
3598        );
3599    }
3600
3601    #[test]
3602    fn query_all_certificates_log_redacts_response_domains() {
3603        const DOMAIN_SECRET: &str = "QUERY_ALL_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3604
3605        // Below `router::MAX_HOSTNAME_LENGTH` so the certificate passes
3606        // add-time name validation; the redaction property under test is
3607        // length-independent.
3608        let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3609        let output = crate::capture_test_logs(move || {
3610            let mut proxy = proxy_with_certificate_domain(domain);
3611            proxy
3612                .query_all_certificates()
3613                .expect("certificate query must succeed");
3614        });
3615
3616        assert!(
3617            !output.contains(DOMAIN_SECRET),
3618            "Certificates::All log leaked response domain {DOMAIN_SECRET}"
3619        );
3620        for metadata in ["listeners_count=1", "certificates_count=1"] {
3621            assert!(
3622                output.contains(metadata),
3623                "Certificates::All log omitted bounded metadata {metadata}: {output}"
3624            );
3625        }
3626        assert!(
3627            output.len() <= 1024,
3628            "Certificates::All log capture is not bounded: {} bytes",
3629            output.len()
3630        );
3631    }
3632
3633    #[test]
3634    fn query_certificate_for_domain_log_redacts_request_and_response_domain() {
3635        const DOMAIN_SECRET: &str = "QUERY_ONE_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3636
3637        // Below `router::MAX_HOSTNAME_LENGTH` -- see
3638        // `query_all_certificates_log_redacts_response_domains`.
3639        let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3640        let domain_len = domain.len();
3641        let output = crate::capture_test_logs_at_level("debug", move || {
3642            let mut proxy = proxy_with_certificate_domain(domain.clone());
3643            let response = proxy.notify(WorkerRequest {
3644                id: "query-domain-redaction-test".to_owned(),
3645                content: RequestType::QueryCertificatesFromWorkers(
3646                    sozu_command::proto::command::QueryCertificatesFilters {
3647                        domain: Some(domain),
3648                        fingerprint: None,
3649                    },
3650                )
3651                .into(),
3652            });
3653            assert_eq!(
3654                response.status,
3655                sozu_command::proto::command::ResponseStatus::Ok as i32,
3656                "domain certificate query must succeed: {}",
3657                response.message
3658            );
3659        });
3660
3661        assert!(
3662            !output.contains(DOMAIN_SECRET),
3663            "Certificates::Domain log leaked request or response domain {DOMAIN_SECRET}"
3664        );
3665        for metadata in [
3666            format!("domain_bytes={domain_len}"),
3667            "listeners_count=1".to_owned(),
3668            "certificates_count=1".to_owned(),
3669        ] {
3670            assert!(
3671                output.contains(&metadata),
3672                "Certificates::Domain log omitted bounded metadata {metadata}: {output}"
3673            );
3674        }
3675        assert!(
3676            output.len() <= 1024,
3677            "Certificates::Domain log capture is not bounded: {} bytes",
3678            output.len()
3679        );
3680    }
3681
3682    #[test]
3683    fn frontend_from_request_test() {
3684        let cluster_id1 = ClusterId::from("cluster_1");
3685        let cluster_id2 = ClusterId::from("cluster_2");
3686        let cluster_id3 = ClusterId::from("cluster_3");
3687        let uri1 = "/".to_owned();
3688        let uri2 = "/yolo".to_owned();
3689        let uri3 = "/yolo/swag".to_owned();
3690
3691        let mut fronts = Router::new();
3692        assert!(fronts.add_tree_rule(
3693            "lolcatho.st".as_bytes(),
3694            &PathRule::Prefix(uri1),
3695            &MethodRule::new(None),
3696            &Route::ClusterId(cluster_id1.clone())
3697        ));
3698        assert!(fronts.add_tree_rule(
3699            "lolcatho.st".as_bytes(),
3700            &PathRule::Prefix(uri2),
3701            &MethodRule::new(None),
3702            &Route::ClusterId(cluster_id2)
3703        ));
3704        assert!(fronts.add_tree_rule(
3705            "lolcatho.st".as_bytes(),
3706            &PathRule::Prefix(uri3),
3707            &MethodRule::new(None),
3708            &Route::ClusterId(cluster_id3)
3709        ));
3710        assert!(fronts.add_tree_rule(
3711            "other.domain".as_bytes(),
3712            &PathRule::Prefix("test".to_string()),
3713            &MethodRule::new(None),
3714            &Route::ClusterId(cluster_id1)
3715        ));
3716
3717        let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
3718        let resolver = Arc::new(MutexCertificateResolver::default());
3719
3720        let crypto_provider = Arc::new(default_provider());
3721
3722        let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
3723            .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
3724            .expect("could not create rustls config server")
3725            .with_no_client_auth()
3726            .with_cert_resolver(resolver.clone());
3727
3728        let rustls_details = Arc::new(server_config);
3729
3730        let default_config = ListenerBuilder::new_https(address)
3731            .to_tls(None)
3732            .expect("Could not create default HTTPS listener config");
3733
3734        println!("it doesn't even matter");
3735
3736        let listener = HttpsListener {
3737            parked_listener: None,
3738            nodelay_backlog: false,
3739            listener: None,
3740            address: address.into(),
3741            fronts,
3742            rustls_details,
3743            resolver,
3744            answers: Rc::new(RefCell::new(
3745                HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
3746            )),
3747            config: default_config,
3748            token: Token(0),
3749            active: true,
3750            tags: BTreeMap::new(),
3751        };
3752
3753        println!("TEST {}", line!());
3754        let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get);
3755        assert_eq!(
3756            frontend1
3757                .expect("should find a frontend")
3758                .cluster_id
3759                .as_deref(),
3760            Some("cluster_1")
3761        );
3762        println!("TEST {}", line!());
3763        let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get);
3764        assert_eq!(
3765            frontend2
3766                .expect("should find a frontend")
3767                .cluster_id
3768                .as_deref(),
3769            Some("cluster_1")
3770        );
3771        println!("TEST {}", line!());
3772        let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get);
3773        assert_eq!(
3774            frontend3
3775                .expect("should find a frontend")
3776                .cluster_id
3777                .as_deref(),
3778            Some("cluster_2")
3779        );
3780        println!("TEST {}", line!());
3781        let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get);
3782        assert_eq!(
3783            frontend4
3784                .expect("should find a frontend")
3785                .cluster_id
3786                .as_deref(),
3787            Some("cluster_3")
3788        );
3789        println!("TEST {}", line!());
3790        let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get);
3791        assert!(frontend5.is_err());
3792        // assert!(false);
3793    }
3794
3795    #[test]
3796    fn wildcard_certificate_names() {
3797        let mut trie = TrieNode::root();
3798
3799        trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
3800        trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
3801        trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
3802        trie.domain_insert(
3803            "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
3804            3u8,
3805        );
3806        trie.domain_insert(
3807            "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
3808            4u8,
3809        );
3810
3811        let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
3812        println!("query result: {res:?}");
3813
3814        assert_eq!(
3815            trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
3816            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3817        );
3818        assert_eq!(
3819            trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
3820            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3821        );
3822    }
3823
3824    #[test]
3825    fn wildcard_with_subdomains() {
3826        let mut trie = TrieNode::root();
3827
3828        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3829        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3830
3831        let res = trie.domain_lookup(b"sub.test.example.com", true);
3832        println!("query result: {res:?}");
3833
3834        assert_eq!(
3835            trie.domain_lookup(b"sub.test.example.com", true),
3836            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3837        );
3838        assert_eq!(
3839            trie.domain_lookup(b"hello.sub.test.example.com", true),
3840            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3841        );
3842
3843        // now try in a different order
3844        let mut trie = TrieNode::root();
3845
3846        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3847        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3848
3849        let res = trie.domain_lookup(b"sub.test.example.com", true);
3850        println!("query result: {res:?}");
3851
3852        assert_eq!(
3853            trie.domain_lookup(b"sub.test.example.com", true),
3854            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3855        );
3856        assert_eq!(
3857            trie.domain_lookup(b"hello.sub.test.example.com", true),
3858            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3859        );
3860    }
3861
3862    /// sozu#322: the HTTPS twin of `lib/src/http.rs`'s
3863    /// `a_listener_with_an_unknown_forwarded_headers_value_is_refused`.
3864    #[test]
3865    fn a_listener_with_an_unknown_forwarded_headers_value_is_refused() {
3866        let build = |forwarded_headers: Option<i32>| {
3867            let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1043))
3868                .to_tls(None)
3869                .expect("default HTTPS listener config");
3870            cfg.forwarded_headers = forwarded_headers;
3871            HttpsListener::try_new(cfg, Token(0))
3872        };
3873        assert!(build(Some(-1)).is_err(), "an unknown mode must be refused");
3874        let listener = build(Some(ForwardedHeaders::None as i32)).expect("a known mode");
3875        assert_eq!(listener.get_forwarded_headers(), ForwardedHeaders::None);
3876    }
3877
3878    /// The HTTPS twin of `lib/src/http.rs`'s
3879    /// `get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold`
3880    /// (sozu-proxy/sozu#1418): the same thirteen positional arguments are
3881    /// spelled out a second time here, so the two copies need the same
3882    /// transposition guard. Every value is distinct.
3883    #[test]
3884    fn get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold() {
3885        let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1042))
3886            .to_tls(None)
3887            .expect("default HTTPS listener config");
3888        cfg.h2_max_rst_stream_per_window = Some(101);
3889        cfg.h2_max_ping_per_window = Some(102);
3890        cfg.h2_max_settings_per_window = Some(103);
3891        cfg.h2_max_empty_data_per_window = Some(104);
3892        cfg.h2_max_window_update_stream0_per_window = Some(105);
3893        cfg.h2_max_continuation_frames = Some(106);
3894        cfg.h2_max_glitch_count = Some(107);
3895        cfg.h2_max_rst_stream_lifetime = Some(108);
3896        cfg.h2_max_rst_stream_abusive_lifetime = Some(109);
3897        cfg.h2_max_rst_stream_emitted_lifetime = Some(110);
3898        cfg.h2_max_header_list_size = Some(111);
3899        cfg.h2_max_header_table_size = Some(112);
3900        cfg.h2_max_header_fields = Some(113);
3901        cfg.h2_stream_refusal_percent = Some(114);
3902
3903        let listener = HttpsListener::try_new(cfg, Token(0)).expect("build listener");
3904        let flood = listener.get_h2_flood_config();
3905
3906        assert_eq!(flood.max_rst_stream_per_window(), 101);
3907        assert_eq!(flood.max_ping_per_window(), 102);
3908        assert_eq!(flood.max_settings_per_window(), 103);
3909        assert_eq!(flood.max_empty_data_per_window(), 104);
3910        assert_eq!(flood.max_window_update_stream0_per_window(), 105);
3911        assert_eq!(flood.max_continuation_frames(), 106);
3912        assert_eq!(flood.max_glitch_count(), 107);
3913        assert_eq!(flood.max_rst_stream_lifetime(), 108);
3914        assert_eq!(flood.max_rst_stream_abusive_lifetime(), 109);
3915        assert_eq!(flood.max_rst_stream_emitted_lifetime(), 110);
3916        assert_eq!(flood.max_header_list_size(), 111);
3917        assert_eq!(flood.max_header_table_size(), 112);
3918        assert_eq!(flood.max_header_fields(), 113);
3919        assert_eq!(flood.stream_refusal_percent(), 114);
3920    }
3921
3922    #[test]
3923    fn h2_stream_idle_timeout_inherits_back_timeout() {
3924        use std::time::Duration;
3925
3926        let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
3927        let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
3928            let mut cfg = ListenerBuilder::new_https(address)
3929                .to_tls(None)
3930                .expect("default HTTPS listener config");
3931            cfg.back_timeout = back_timeout;
3932            cfg.h2_stream_idle_timeout_seconds = explicit;
3933            HttpsListener::try_new(cfg, Token(0)).expect("build listener")
3934        };
3935
3936        // Knob unset: inherit back_timeout when it exceeds the 30s floor.
3937        assert_eq!(
3938            build(180, None).get_h2_stream_idle_timeout(),
3939            Duration::from_secs(180)
3940        );
3941
3942        // Knob unset, back_timeout below floor: stay at 30s.
3943        assert_eq!(
3944            build(5, None).get_h2_stream_idle_timeout(),
3945            Duration::from_secs(30)
3946        );
3947
3948        // Explicit values win in both directions.
3949        assert_eq!(
3950            build(180, Some(10)).get_h2_stream_idle_timeout(),
3951            Duration::from_secs(10)
3952        );
3953        assert_eq!(
3954            build(5, Some(600)).get_h2_stream_idle_timeout(),
3955            Duration::from_secs(600)
3956        );
3957
3958        // `Some(0)` is clamped to 1s.
3959        assert_eq!(
3960            build(180, Some(0)).get_h2_stream_idle_timeout(),
3961            Duration::from_secs(1)
3962        );
3963    }
3964
3965    // ── FrontRustls peer snapshot (the PROXY-protocol seeding decision) ──
3966
3967    /// Source address a PROXY v2 header advertises. Non-loopback so it can
3968    /// never collide with the address of the connection the test really opens.
3969    const PROXY_ADVERTISED_PEER: &str = "10.0.0.42:12345";
3970
3971    /// The peer snapshot handed to [`FrontRustls`] is the session's known peer
3972    /// address — on an expect-proxy frontend, the source the PROXY header
3973    /// advertised — and NOT `getpeername(2)` on the accepted socket, which on
3974    /// such a frontend is the load balancer.
3975    ///
3976    /// This is the one substitution a future editor is most likely to make
3977    /// here. Hoisting `let raw_peer = handshake.stream.peer_addr().ok();` above
3978    /// the struct literal and seeding from that compiles, passes every other
3979    /// test in this file, and silently restores the split this change exists to
3980    /// remove: the `HTTPS` line naming the client while the `MUX-*` lines name
3981    /// the load balancer, for one and the same request id.
3982    ///
3983    /// The test drives the real `upgrade_handshake`, so it pins the seeding
3984    /// site rather than the accessor. `session.peer_address` is set the way
3985    /// `upgrade_expect` sets it (https.rs, `self.peer_address =
3986    /// Some(session_address)` on the PROXY-header branch) instead of feeding a
3987    /// real PROXY header — that adoption already has its own `debug_assert_eq!`
3988    /// in `upgrade_expect` and its own e2e coverage; what is unguarded, and
3989    /// what this test guards, is the hand-off from there into the socket
3990    /// handler. `handshake.peer_address` is set to the same value because
3991    /// `upgrade_expect` passes `self.peer_address` straight into
3992    /// `TlsHandshake::new`: in production the two are always equal, so the test
3993    /// must not be able to pass or fail on the difference between them.
3994    ///
3995    /// To SEE THIS RED: in `upgrade_handshake`, put
3996    /// `let raw_peer = handshake.stream.peer_addr().ok();` before the
3997    /// `FrontRustls` literal and replace `configured_peer: self.peer_address,`
3998    /// with `configured_peer: raw_peer,`. (Reading `handshake.stream` inside
3999    /// the literal itself does not compile — the `stream:` field has already
4000    /// moved it — which is the only reason the mutation needs the hoist.)
4001    #[test]
4002    fn front_rustls_peer_snapshot_is_the_session_peer_not_the_accepted_socket() {
4003        let advertised: StdSocketAddr = PROXY_ADVERTISED_PEER
4004            .parse()
4005            .expect("the advertised peer literal must parse");
4006
4007        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4008        let listener = proxy
4009            .listeners
4010            .values()
4011            .next()
4012            .expect("the test proxy has one listener")
4013            .clone();
4014        let pool = Rc::downgrade(&proxy.pool);
4015        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4016            .expect("a test rustls server session must be created");
4017        let public_address = *listener.borrow().get_addr();
4018        let proxy = Rc::new(RefCell::new(proxy));
4019
4020        // A genuinely established loopback connection stands in for the one the
4021        // load balancer opened. `getpeername(2)` on it answers the balancer's
4022        // address and keeps answering it — the point being that the snapshot
4023        // must disagree with a live lookup that is perfectly healthy.
4024        let balancer = std::net::TcpListener::bind("127.0.0.1:0")
4025            .expect("test listener must bind to a loopback port");
4026        let balancer_peer = balancer
4027            .local_addr()
4028            .expect("test listener must report its local address");
4029        let stream =
4030            std::net::TcpStream::connect(balancer_peer).expect("loopback connect must complete");
4031        stream
4032            .set_nonblocking(true)
4033            .expect("mio requires a nonblocking stream");
4034        let stream = MioTcpStream::from_std(stream);
4035
4036        let mut session = HttpsSession::new(
4037            Duration::from_secs(30),
4038            Duration::from_secs(30),
4039            Duration::from_secs(30),
4040            Duration::from_secs(30),
4041            // Built on the direct route so the session owns exactly one stream
4042            // and one rustls session; the PROXY adoption is then applied below.
4043            false,
4044            listener,
4045            pool,
4046            proxy,
4047            public_address,
4048            rustls_details,
4049            stream,
4050            // What `accept(2)` reports for this connection: the far end, which
4051            // for the stand-in is the balancer.
4052            balancer_peer,
4053            Token(0),
4054            Duration::from_secs(0),
4055        );
4056
4057        assert_eq!(
4058            session.peer_address,
4059            Some(balancer_peer),
4060            "a direct-route session starts out with the accepted socket's peer"
4061        );
4062
4063        let mut handshake = match session.state.take() {
4064            HttpsStateMachine::Handshake(handshake) => handshake,
4065            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4066        };
4067
4068        // What `upgrade_expect` does once the PROXY header parses.
4069        session.peer_address = Some(advertised);
4070        handshake.peer_address = Some(advertised);
4071        assert_ne!(
4072            advertised, balancer_peer,
4073            "the advertised and connection peers must differ for this test to discriminate"
4074        );
4075
4076        let upgraded = session
4077            .upgrade_handshake(handshake)
4078            .expect("the handshake must upgrade to a mux session");
4079        // Borrowed rather than moved out: `ConnectionH2` implements `Drop`, so
4080        // its `socket` field cannot leave the connection.
4081        let (snapshot, live_lookup) = match &upgraded {
4082            HttpsStateMachine::Mux(mux) => match &mux.frontend {
4083                mux::Connection::H1(connection) => (
4084                    connection.socket.peer_addr(),
4085                    connection.socket.socket_ref().peer_addr().ok(),
4086                ),
4087                mux::Connection::H2(connection) => (
4088                    connection.socket.peer_addr(),
4089                    connection.socket.socket_ref().peer_addr().ok(),
4090                ),
4091            },
4092            _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4093        };
4094
4095        // Premise: the live lookup is healthy and still reports the balancer,
4096        // so the assertion below cannot pass for the wrong reason.
4097        assert_eq!(
4098            live_lookup,
4099            Some(balancer_peer),
4100            "the frontend socket must still be connected to the stand-in balancer"
4101        );
4102        assert_eq!(
4103            snapshot,
4104            Some(advertised),
4105            "FrontRustls must carry the PROXY-advertised source, not the accepted socket's peer"
4106        );
4107    }
4108
4109    /// The `or_else` arm of [`FrontRustls::peer_addr`] answers for a handler
4110    /// built with no peer address. No production route builds one any more:
4111    /// the direct (non-expect-proxy) route seeds `peer_address` with the
4112    /// address `accept(2)` returned (sozu-proxy/sozu#1586), and the
4113    /// expect-proxy route only reaches the handshake with the PROXY-advertised
4114    /// source. The arm stays as defence in depth — without it a `None` would
4115    /// blank the `peer=` slot for the whole connection even though the socket
4116    /// is healthy and the kernel would answer — so the test builds that
4117    /// handler by hand.
4118    ///
4119    /// `SessionTcpStream`'s identical arm has had
4120    /// `socket::tests::session_tcp_stream_peer_addr_falls_back_to_the_live_lookup`
4121    /// since it was written; this is the missing twin for `FrontRustls`.
4122    ///
4123    /// To SEE THIS RED: in `impl SocketHandler for FrontRustls`, drop the
4124    /// `.or_else(|| self.stream.peer_addr().ok())` from `peer_addr`. The
4125    /// handler then answers `None` for a perfectly healthy connection.
4126    #[test]
4127    fn front_rustls_peer_addr_falls_back_to_the_live_lookup() {
4128        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4129        let listener = proxy
4130            .listeners
4131            .values()
4132            .next()
4133            .expect("the test proxy has one listener")
4134            .clone();
4135        let pool = Rc::downgrade(&proxy.pool);
4136        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4137            .expect("a test rustls server session must be created");
4138        let public_address = *listener.borrow().get_addr();
4139        let proxy = Rc::new(RefCell::new(proxy));
4140
4141        let peer = std::net::TcpListener::bind("127.0.0.1:0")
4142            .expect("test listener must bind to a loopback port");
4143        let live_peer = peer
4144            .local_addr()
4145            .expect("test listener must report its local address");
4146        let stream =
4147            std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
4148        stream
4149            .set_nonblocking(true)
4150            .expect("mio requires a nonblocking stream");
4151        let stream = MioTcpStream::from_std(stream);
4152
4153        let mut session = HttpsSession::new(
4154            Duration::from_secs(30),
4155            Duration::from_secs(30),
4156            Duration::from_secs(30),
4157            Duration::from_secs(30),
4158            false,
4159            listener,
4160            pool,
4161            proxy,
4162            public_address,
4163            rustls_details,
4164            stream,
4165            live_peer,
4166            Token(0),
4167            Duration::from_secs(0),
4168        );
4169
4170        let mut handshake = match session.state.take() {
4171            HttpsStateMachine::Handshake(handshake) => handshake,
4172            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4173        };
4174
4175        // A session that knows no peer, which the handler must fall back from.
4176        session.peer_address = None;
4177        handshake.peer_address = None;
4178
4179        let upgraded = session
4180            .upgrade_handshake(handshake)
4181            .expect("the handshake must upgrade to a mux session");
4182        let snapshot = match &upgraded {
4183            HttpsStateMachine::Mux(mux) => match &mux.frontend {
4184                mux::Connection::H1(connection) => connection.socket.peer_addr(),
4185                mux::Connection::H2(connection) => connection.socket.peer_addr(),
4186            },
4187            _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4188        };
4189
4190        assert_eq!(
4191            snapshot,
4192            Some(live_peer),
4193            "with nothing cached, FrontRustls::peer_addr must degrade to getpeername(2)"
4194        );
4195    }
4196
4197    /// `sozu query certificates --domain <d>` must ask the resolver the
4198    /// same question a TLS handshake asks it.
4199    ///
4200    /// The resolver keys its SNI trie on ASCII-lowercased certificate
4201    /// names (`CertifiedKeyWrapper::try_from`, `lib/src/tls.rs`), because
4202    /// rustls hands `ResolvesServerCert::resolve` an SNI it has already
4203    /// lowercased and no other key can ever be looked up at handshake
4204    /// time. The operator query has to be normalised the same way, or
4205    /// `sozu query certificates --domain MiXeD.Example.COM` reports the
4206    /// certificate missing while the proxy is serving it on every
4207    /// handshake for that name.
4208    ///
4209    /// To SEE THIS RED: in `query_certificate_for_domain`, look the
4210    /// domain up with `domain.as_bytes()` instead of
4211    /// `domain_key.as_bytes()`. The two case-varying queries return zero
4212    /// summaries.
4213    #[test]
4214    fn query_certificate_for_domain_answers_any_spelling_of_the_name() {
4215        let mut proxy = proxy_with_certificate_domain("MiXeD.Example.COM".to_owned());
4216
4217        let mut summaries_for = |query: &str| {
4218            let response = proxy
4219                .query_certificate_for_domain(query.to_owned())
4220                .expect("the domain certificate query must succeed")
4221                .expect("the domain certificate query must return content");
4222            match response.content_type {
4223                Some(ContentType::CertificatesByAddress(list)) => list
4224                    .certificates
4225                    .iter()
4226                    .map(|entry| entry.certificate_summaries.len())
4227                    .sum::<usize>(),
4228                _ => panic!("the domain query must answer with CertificatesByAddress"),
4229            }
4230        };
4231
4232        for query in [
4233            "MiXeD.Example.COM",
4234            "mixed.example.com",
4235            "MIXED.EXAMPLE.COM",
4236        ] {
4237            assert_eq!(
4238                summaries_for(query),
4239                1,
4240                "querying {query:?} must find the certificate stored as MiXeD.Example.COM",
4241            );
4242        }
4243
4244        assert_eq!(
4245            summaries_for("other.example.com"),
4246            0,
4247            "normalising the query must not make an unrelated domain match",
4248        );
4249    }
4250
4251    /// An HTTPS session upgraded to the mux over a live loopback connection,
4252    /// with the accepted peer standing in for the client. The TLS handshake
4253    /// never ran; nothing here reads what the client receives beyond the FIN.
4254    fn https_mux_session_with_peer() -> (HttpsSession, std::net::TcpStream) {
4255        let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4256        let listener = proxy
4257            .listeners
4258            .values()
4259            .next()
4260            .expect("the test proxy has one listener")
4261            .clone();
4262        let pool = Rc::downgrade(&proxy.pool);
4263        let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4264            .expect("a test rustls server session must be created");
4265        let public_address = *listener.borrow().get_addr();
4266        let proxy = Rc::new(RefCell::new(proxy));
4267
4268        let accepting = std::net::TcpListener::bind("127.0.0.1:0")
4269            .expect("test listener must bind to a loopback port");
4270        let address = accepting
4271            .local_addr()
4272            .expect("test listener must report its local address");
4273        let stream = std::net::TcpStream::connect(address).expect("loopback connect must complete");
4274        let (client, client_address) = accepting.accept().expect("loopback accept must complete");
4275        client
4276            .set_nonblocking(true)
4277            .expect("the client stand-in must be nonblocking");
4278        stream
4279            .set_nonblocking(true)
4280            .expect("mio requires a nonblocking stream");
4281
4282        let mut session = HttpsSession::new(
4283            Duration::from_secs(30),
4284            Duration::from_secs(30),
4285            Duration::from_secs(30),
4286            Duration::from_secs(30),
4287            false,
4288            listener,
4289            pool,
4290            proxy,
4291            public_address,
4292            rustls_details,
4293            MioTcpStream::from_std(stream),
4294            client_address,
4295            Token(0),
4296            Duration::from_secs(0),
4297        );
4298        let handshake = match session.state.take() {
4299            HttpsStateMachine::Handshake(handshake) => handshake,
4300            _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4301        };
4302        session.state = session
4303            .upgrade_handshake(handshake)
4304            .expect("the handshake must upgrade to a mux session");
4305        (session, client)
4306    }
4307
4308    /// Close `session`, drop it as the event loop does, and return the
4309    /// `shutdown(SHUT_WR)` calls the close issued and whether `client` then
4310    /// observed the FIN.
4311    fn frontend_shutdowns_of_a_close(
4312        mut session: HttpsSession,
4313        client: &mut std::net::TcpStream,
4314    ) -> (usize, bool) {
4315        use std::io::Read;
4316
4317        let before = mux::shutdown_writes();
4318        session.close();
4319        let shutdowns = mux::shutdown_writes() - before;
4320        drop(session);
4321
4322        let deadline = Instant::now() + Duration::from_secs(2);
4323        let mut chunk = [0u8; 4096];
4324        let saw_fin = loop {
4325            match client.read(&mut chunk) {
4326                Ok(0) => break true,
4327                Ok(_) => {}
4328                Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
4329                    std::thread::sleep(Duration::from_millis(5));
4330                }
4331                Err(_) => break false,
4332            }
4333        };
4334        (shutdowns, saw_fin)
4335    }
4336
4337    /// Issue #1603: once the client has closed its side, `HttpsSession::close`
4338    /// does not `shutdown(SHUT_WR)` the frontend socket — on an H2 client that
4339    /// had already reset, that call failed with `ENOTCONN` on every
4340    /// connection. The client, which only half-closed here and is still
4341    /// reading, gets its FIN from the close alone.
4342    ///
4343    /// TO SEE THIS RED: make `mux::shutdown_write` ignore `peer_closed`, or
4344    /// drop the `peer_disconnected` arm from `HttpsSession::close` (then only
4345    /// the `read EOF` case fails). The first assertion fails with `left: 1`,
4346    /// `right: 0`.
4347    ///
4348    /// Two ways to know it: the HUP the event loop records for the client's
4349    /// FIN, and `FrontRustls::peer_disconnected`, set by the read that met the
4350    /// EOF or the `close_notify` — the H2 case, where the close follows in
4351    /// the same pass and no epoll event reports it.
4352    #[test]
4353    fn https_close_skips_the_frontend_shutdown_once_the_client_closed() {
4354        for how in ["epoll HUP", "read EOF"] {
4355            let (mut session, mut client) = https_mux_session_with_peer();
4356            client
4357                .shutdown(std::net::Shutdown::Write)
4358                .expect("the client stand-in must send its FIN");
4359            let HttpsStateMachine::Mux(mux) = &mut session.state else {
4360                unreachable!("https_mux_session_with_peer returns a mux session")
4361            };
4362            if how == "epoll HUP" {
4363                mux.frontend.readiness_mut().event.insert(Ready::HUP);
4364            } else {
4365                match &mut mux.frontend {
4366                    mux::Connection::H1(connection) => connection.socket.peer_disconnected = true,
4367                    mux::Connection::H2(connection) => connection.socket.peer_disconnected = true,
4368                }
4369            }
4370
4371            let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4372            assert_eq!(
4373                shutdowns, 0,
4374                "a frontend whose client closed ({how}) must not be shut down before its close"
4375            );
4376            assert!(
4377                saw_fin,
4378                "the close alone must still deliver the FIN to a half-closed client ({how})"
4379            );
4380        }
4381    }
4382
4383    /// The other half of #1603: a client that is still there keeps the
4384    /// frontend `shutdown(SHUT_WR)` and observes the FIN.
4385    #[test]
4386    fn https_close_keeps_the_frontend_shutdown_while_the_client_is_there() {
4387        let (session, mut client) = https_mux_session_with_peer();
4388        let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4389        assert_eq!(
4390            shutdowns, 1,
4391            "a frontend whose client is still there must keep its shutdown(SHUT_WR)"
4392        );
4393        assert!(saw_fin, "the client must observe the FIN");
4394    }
4395}
4396
4397#[cfg(test)]
4398mod listener_sibling_tests {
4399    use sozu_command::{
4400        config::ListenerBuilder,
4401        proto::command::{
4402            CertificateAndKey, HstsConfig, PathRule, RequestHttpFrontend, SocketAddress,
4403        },
4404    };
4405
4406    use super::*;
4407    use crate::testing::{ServerParts, prebuild_server, provide_port};
4408
4409    fn proxy() -> HttpsProxy {
4410        let ServerParts {
4411            registry,
4412            sessions,
4413            pool,
4414            backends,
4415            ..
4416        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4417        HttpsProxy::new(registry, sessions, pool, backends)
4418    }
4419
4420    fn add(proxy: &mut HttpsProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
4421        let config = ListenerBuilder::new_https(address)
4422            .with_interface(interface)
4423            .to_tls(None)
4424            .expect("could not build the listener config");
4425        proxy
4426            .add_listener(config, Token(token))
4427            .expect("could not add the listener");
4428    }
4429
4430    fn certificate(address: SocketAddress) -> AddCertificate {
4431        AddCertificate {
4432            address,
4433            certificate: CertificateAndKey {
4434                certificate: include_str!("../assets/certificate.pem").to_owned(),
4435                key: include_str!("../assets/key.pem").to_owned(),
4436                certificate_chain: Vec::new(),
4437                versions: Vec::new(),
4438                names: vec!["example.com".to_owned()],
4439            },
4440            expired_at: None,
4441        }
4442    }
4443
4444    fn front(address: SocketAddress) -> RequestHttpFrontend {
4445        RequestHttpFrontend {
4446            cluster_id: Some("cluster-a".to_owned()),
4447            address,
4448            hostname: "example.com".to_owned(),
4449            path: PathRule::prefix("/".to_owned()),
4450            ..Default::default()
4451        }
4452    }
4453
4454    fn serves_certificate(proxy: &HttpsProxy, token: usize) -> bool {
4455        proxy.listeners[&Token(token)]
4456            .borrow()
4457            .resolver
4458            .0
4459            .lock()
4460            .expect("resolver lock")
4461            .domains
4462            .domain_lookup(b"example.com", true)
4463            .is_some()
4464    }
4465
4466    fn routes(proxy: &HttpsProxy, token: usize) -> bool {
4467        proxy.listeners[&Token(token)]
4468            .borrow()
4469            .fronts
4470            .lookup("example.com", "/", &Method::Get)
4471            .is_ok()
4472    }
4473
4474    /// An HTTPS listener added on an address that already serves a certificate
4475    /// and a frontend takes both over from the listener already there, so it
4476    /// completes TLS handshakes and routes from its first connection
4477    /// (sozu-proxy/sozu#719). It gets its own copy of the certificates: a
4478    /// later change reaches it through the fan-out, like its sibling.
4479    #[test]
4480    fn a_listener_added_next_to_a_sibling_takes_over_its_certificates_and_frontends() {
4481        let mut proxy = proxy();
4482        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4483        add(&mut proxy, address, None, 0);
4484        proxy
4485            .add_certificate(certificate(address))
4486            .expect("add the certificate");
4487        proxy
4488            .add_https_frontend(front(address))
4489            .expect("add the frontend");
4490        add(&mut proxy, address, Some("lo"), 1);
4491
4492        assert!(serves_certificate(&proxy, 0));
4493        assert!(
4494            serves_certificate(&proxy, 1),
4495            "the listener added after the certificate must serve it too"
4496        );
4497        assert!(
4498            routes(&proxy, 1),
4499            "the listener added after the frontend must route it too"
4500        );
4501
4502        proxy
4503            .remove_https_frontend(front(address))
4504            .expect("remove the frontend");
4505        assert!(!routes(&proxy, 0));
4506        assert!(!routes(&proxy, 1));
4507    }
4508    fn hsts(max_age: u32) -> HstsConfig {
4509        HstsConfig {
4510            enabled: Some(true),
4511            max_age: Some(max_age),
4512            ..Default::default()
4513        }
4514    }
4515
4516    fn add_with_hsts(
4517        proxy: &mut HttpsProxy,
4518        address: SocketAddress,
4519        interface: Option<&str>,
4520        token: usize,
4521        hsts: Option<HstsConfig>,
4522    ) {
4523        let mut config = ListenerBuilder::new_https(address)
4524            .with_interface(interface)
4525            .to_tls(None)
4526            .expect("could not build the listener config");
4527        config.hsts = hsts;
4528        proxy
4529            .add_listener(config, Token(token))
4530            .expect("could not add the listener");
4531    }
4532
4533    /// A frontend carrying policy (here, tags) so the router stores it as a
4534    /// `Route::Frontend` rather than the lightweight `Route::ClusterId`.
4535    fn tagged_front(address: SocketAddress) -> RequestHttpFrontend {
4536        RequestHttpFrontend {
4537            tags: BTreeMap::from([("owner".to_owned(), "team-a".to_owned())]),
4538            ..front(address)
4539        }
4540    }
4541
4542    /// The `Strict-Transport-Security` value the listener emits for
4543    /// `example.com/`, if any.
4544    fn served_hsts(proxy: &HttpsProxy, token: usize) -> Option<String> {
4545        let route = proxy.listeners[&Token(token)]
4546            .borrow()
4547            .fronts
4548            .lookup("example.com", "/", &Method::Get)
4549            .expect("the listener must route example.com");
4550        route
4551            .headers_response
4552            .iter()
4553            .find(|edit| edit.key.eq_ignore_ascii_case(b"strict-transport-security"))
4554            .map(|edit| String::from_utf8_lossy(&edit.val).into_owned())
4555    }
4556
4557    fn patch_hsts(proxy: &HttpsProxy, token: usize, address: SocketAddress, hsts: HstsConfig) {
4558        proxy.listeners[&Token(token)]
4559            .borrow_mut()
4560            .update_config(&UpdateHttpsListenerConfig {
4561                address,
4562                hsts: Some(hsts),
4563                ..Default::default()
4564            })
4565            .expect("patch the listener HSTS default");
4566    }
4567
4568    /// A patch the worker refuses leaves the HTTPS listener exactly as it was
4569    /// (sozu-proxy/sozu#1703): its configuration, its rustls context, its
4570    /// answer registry and the HSTS its frontends serve. Each patch carries
4571    /// fields that validate — an ALPN change among them, which rebuilds the
4572    /// rustls context — then one refused field: an `hsts` block without
4573    /// `enabled`, which the old code checked after committing every other
4574    /// field, the new ALPN context and the new templates, or an answer
4575    /// template that does not parse.
4576    ///
4577    /// To SEE THIS RED: in `HttpsListener::update_config`, move the
4578    /// `HstsEnabledRequired` check back to the HSTS block at the end, and
4579    /// write the fields into `self.config` instead of the candidate copy.
4580    #[test]
4581    fn a_rejected_patch_leaves_the_listener_unchanged() {
4582        use sozu_command::proto::command::{AlpnProtocols, UpdateHttpsListenerConfig};
4583
4584        let mut proxy = proxy();
4585        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4586        add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4587        proxy
4588            .add_https_frontend(tagged_front(address))
4589            .expect("add the frontend");
4590
4591        let valid_prefix = UpdateHttpsListenerConfig {
4592            address,
4593            front_timeout: Some(99),
4594            sozu_id_header: Some("X-Edge-Id".to_owned()),
4595            alpn_protocols: Some(AlpnProtocols {
4596                values: vec!["http/1.1".to_owned()],
4597            }),
4598            answers: BTreeMap::from([(
4599                "404".to_owned(),
4600                "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_owned(),
4601            )]),
4602            ..Default::default()
4603        };
4604        let rejected = [
4605            UpdateHttpsListenerConfig {
4606                hsts: Some(HstsConfig {
4607                    enabled: None,
4608                    max_age: Some(200),
4609                    ..Default::default()
4610                }),
4611                ..valid_prefix.clone()
4612            },
4613            UpdateHttpsListenerConfig {
4614                answers: BTreeMap::from([(
4615                    "503".to_owned(),
4616                    "not a valid http response".to_owned(),
4617                )]),
4618                hsts: Some(hsts(200)),
4619                ..valid_prefix.clone()
4620            },
4621        ];
4622        for patch in rejected {
4623            let listener = &proxy.listeners[&Token(0)];
4624            let config_before = listener.borrow().config.clone();
4625            let rustls_before = listener.borrow().rustls_details.clone();
4626            let answers_before = listener.borrow().get_answers().clone();
4627            listener
4628                .borrow_mut()
4629                .update_config(&patch)
4630                .expect_err("the patch must be refused");
4631            let listener = listener.borrow();
4632            assert_eq!(
4633                listener.config, config_before,
4634                "a refused patch must leave the listener configuration unchanged"
4635            );
4636            assert!(
4637                Arc::ptr_eq(&rustls_before, &listener.rustls_details),
4638                "a refused patch must not publish a new rustls context"
4639            );
4640            assert!(
4641                Rc::ptr_eq(&answers_before, listener.get_answers()),
4642                "a refused patch must not publish a new answer registry"
4643            );
4644            drop(listener);
4645            assert_eq!(
4646                served_hsts(&proxy, 0).as_deref(),
4647                Some("max-age=100"),
4648                "a refused patch must not refresh the frontends' HSTS"
4649            );
4650        }
4651
4652        proxy.listeners[&Token(0)]
4653            .borrow_mut()
4654            .update_config(&valid_prefix)
4655            .expect("the valid prefix alone must apply");
4656        let listener = proxy.listeners[&Token(0)].borrow();
4657        assert_eq!(listener.config.front_timeout, 99);
4658        assert_eq!(listener.config.alpn_protocols, vec!["http/1.1".to_owned()]);
4659    }
4660
4661    /// A policy-carrying frontend with no `hsts` of its own, added while the
4662    /// listener on its address had no HSTS default, is copied into a listener
4663    /// added later on that address. Each listener serves its own default: the
4664    /// new listener its HSTS header, the first one none (sozu-proxy/sozu#1691).
4665    #[test]
4666    fn a_copied_frontend_without_hsts_inherits_the_new_listener_default() {
4667        let mut proxy = proxy();
4668        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4669        add_with_hsts(&mut proxy, address, None, 0, None);
4670        proxy
4671            .add_https_frontend(tagged_front(address))
4672            .expect("add the frontend");
4673        add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(100)));
4674
4675        assert_eq!(served_hsts(&proxy, 0), None);
4676        assert_eq!(
4677            served_hsts(&proxy, 1).as_deref(),
4678            Some("max-age=100"),
4679            "the listener added after the frontend must serve its own HSTS default"
4680        );
4681
4682        // Both copies keep following their own listener's default.
4683        patch_hsts(&proxy, 0, address, hsts(200));
4684        patch_hsts(&proxy, 1, address, hsts(300));
4685        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=200"));
4686        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=300"));
4687    }
4688
4689    /// A frontend with its own `hsts` keeps it on every listener of its
4690    /// address, whatever their defaults, at copy time and on later patches.
4691    #[test]
4692    fn a_frontend_with_explicit_hsts_is_never_overridden_by_a_listener_default() {
4693        let mut proxy = proxy();
4694        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4695        add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4696        proxy
4697            .add_https_frontend(RequestHttpFrontend {
4698                hsts: Some(hsts(42)),
4699                ..tagged_front(address)
4700            })
4701            .expect("add the frontend");
4702        add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(300)));
4703
4704        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4705        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4706
4707        patch_hsts(&proxy, 0, address, hsts(500));
4708        patch_hsts(&proxy, 1, address, hsts(600));
4709        assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4710        assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4711    }
4712
4713    /// On a single listener, patching the HSTS default reaches a
4714    /// policy-carrying frontend that declared no `hsts` of its own, whether
4715    /// the listener had a default when the frontend was added or not.
4716    #[test]
4717    fn a_listener_hsts_patch_refreshes_every_frontend_without_its_own_hsts() {
4718        for initial in [None, Some(hsts(100))] {
4719            let mut proxy = proxy();
4720            let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4721            add_with_hsts(&mut proxy, address, None, 0, initial);
4722            proxy
4723                .add_https_frontend(tagged_front(address))
4724                .expect("add the frontend");
4725
4726            patch_hsts(&proxy, 0, address, hsts(700));
4727            assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=700"));
4728
4729            patch_hsts(
4730                &proxy,
4731                0,
4732                address,
4733                HstsConfig {
4734                    enabled: Some(false),
4735                    ..Default::default()
4736                },
4737            );
4738            assert_eq!(served_hsts(&proxy, 0), None);
4739        }
4740    }
4741
4742    /// A self-signed cert usable as a CA root anchor in the verifier tests.
4743    const TEST_CA_PEM: &str = include_str!("../assets/certificate.pem");
4744
4745    fn test_crypto_provider() -> Arc<CryptoProvider> {
4746        Arc::new(default_provider())
4747    }
4748
4749    fn https_config_with_client_auth(
4750        mode: ClientAuthMode,
4751        cas: &[&str],
4752        crls: &[&str],
4753    ) -> HttpsListenerConfig {
4754        let address = SocketAddress::new_v4(127, 0, 0, 1, 1049);
4755        let mut config = ListenerBuilder::new_https(address)
4756            .to_tls(None)
4757            .expect("default HTTPS listener config");
4758        config.client_auth = Some(mode as i32);
4759        config.client_ca_certificates = cas.iter().map(|s| s.to_string()).collect();
4760        config.client_ca_crls = crls.iter().map(|s| s.to_string()).collect();
4761        config
4762    }
4763
4764    #[test]
4765    fn client_cert_verifier_none_is_disabled() {
4766        // NONE (and an absent field) must keep the no-client-auth path.
4767        let provider = test_crypto_provider();
4768        let config = https_config_with_client_auth(ClientAuthMode::ClientAuthNone, &[], &[]);
4769        assert!(
4770            HttpsListener::client_cert_verifier(&config, &provider)
4771                .expect("verifier build")
4772                .is_none()
4773        );
4774
4775        let mut absent = config;
4776        absent.client_auth = None;
4777        assert!(
4778            HttpsListener::client_cert_verifier(&absent, &provider)
4779                .expect("verifier build")
4780                .is_none(),
4781            "absent client_auth must decode to NONE"
4782        );
4783    }
4784
4785    #[test]
4786    fn client_cert_verifier_rejects_unknown_mode() {
4787        // An unknown (future/garbage) enum value must be rejected, never fold
4788        // to NONE and silently accept unauthenticated clients.
4789        let provider = test_crypto_provider();
4790        let mut config =
4791            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4792        config.client_auth = Some(999);
4793        assert!(matches!(
4794            HttpsListener::client_cert_verifier(&config, &provider),
4795            Err(ListenerError::ClientAuth(_))
4796        ));
4797    }
4798
4799    #[test]
4800    fn client_cert_verifier_required_builds_with_ca() {
4801        let provider = test_crypto_provider();
4802        let config =
4803            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4804        assert!(
4805            HttpsListener::client_cert_verifier(&config, &provider)
4806                .expect("verifier build")
4807                .is_some()
4808        );
4809    }
4810
4811    #[test]
4812    fn client_cert_verifier_optional_builds_with_ca() {
4813        let provider = test_crypto_provider();
4814        let config =
4815            https_config_with_client_auth(ClientAuthMode::ClientAuthOptional, &[TEST_CA_PEM], &[]);
4816        assert!(
4817            HttpsListener::client_cert_verifier(&config, &provider)
4818                .expect("verifier build")
4819                .is_some()
4820        );
4821    }
4822
4823    #[test]
4824    fn client_cert_verifier_requires_a_trusted_ca() {
4825        // A non-NONE mode with an empty CA bundle is a configuration error, not
4826        // a silently-permissive listener.
4827        let provider = test_crypto_provider();
4828        let config = https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[], &[]);
4829        assert!(matches!(
4830            HttpsListener::client_cert_verifier(&config, &provider),
4831            Err(ListenerError::ClientAuth(_))
4832        ));
4833    }
4834
4835    #[test]
4836    fn client_cert_verifier_rejects_malformed_ca() {
4837        let provider = test_crypto_provider();
4838        let config = https_config_with_client_auth(
4839            ClientAuthMode::ClientAuthRequired,
4840            &["not a pem certificate"],
4841            &[],
4842        );
4843        // A CA entry that parses to zero anchors is a configuration error.
4844        assert!(matches!(
4845            HttpsListener::client_cert_verifier(&config, &provider),
4846            Err(ListenerError::ClientAuth(_))
4847        ));
4848    }
4849
4850    #[test]
4851    fn client_cert_verifier_rejects_empty_ca_entry_among_valid_ones() {
4852        // A valid CA followed by an entry contributing no certificate must be
4853        // rejected, not silently dropped, so the configured trust set is never
4854        // quietly reduced to a subset.
4855        let provider = test_crypto_provider();
4856        let config = https_config_with_client_auth(
4857            ClientAuthMode::ClientAuthRequired,
4858            &[TEST_CA_PEM, "not a certificate"],
4859            &[],
4860        );
4861        assert!(matches!(
4862            HttpsListener::client_cert_verifier(&config, &provider),
4863            Err(ListenerError::ClientAuth(_))
4864        ));
4865    }
4866
4867    #[test]
4868    fn client_cert_verifier_rejects_empty_crl_entry() {
4869        // A configured CRL entry that yields zero CRLs would silently disable
4870        // revocation; it must be rejected rather than skipped.
4871        let provider = test_crypto_provider();
4872        let config = https_config_with_client_auth(
4873            ClientAuthMode::ClientAuthRequired,
4874            &[TEST_CA_PEM],
4875            &["not a crl"],
4876        );
4877        assert!(matches!(
4878            HttpsListener::client_cert_verifier(&config, &provider),
4879            Err(ListenerError::ClientAuth(_))
4880        ));
4881    }
4882
4883    // ── Client certificate revocation ──
4884    //
4885    // The fixtures come from `lib/assets/mtls/generate.sh`: one CA, one
4886    // client it issued, and CRLs that each leave exactly one reason to accept
4887    // or reject that client. The verdict is checked on the rustls error, not
4888    // only on accept/reject, because a handshake rejected for the wrong
4889    // reason would still look like a pass at the TLS level.
4890
4891    const MTLS_CA_PEM: &str = include_str!("../assets/mtls/ca-cert.pem");
4892    const MTLS_CLIENT_PEM: &str = include_str!("../assets/mtls/client-cert.pem");
4893    /// Issued by the CA, nextUpdate in 2125, revokes nothing.
4894    const MTLS_CRL_CURRENT: &str = include_str!("../assets/mtls/crl-current.pem");
4895    /// Issued by the CA, nextUpdate in 2125, revokes the client.
4896    const MTLS_CRL_REVOKED: &str = include_str!("../assets/mtls/crl-revoked.pem");
4897    /// Issued by the CA, nextUpdate in January 2020, revokes nothing.
4898    const MTLS_CRL_EXPIRED: &str = include_str!("../assets/mtls/crl-expired.pem");
4899    /// Issued by another CA: covers nothing in the client's chain.
4900    const MTLS_CRL_OTHER_CA: &str = include_str!("../assets/mtls/crl-other-ca.pem");
4901    /// Two partitions of the CA's list, each with its own
4902    /// IssuingDistributionPoint, neither covering the client.
4903    const MTLS_CRL_PARTITION_A: &str = include_str!("../assets/mtls/crl-partition-a.pem");
4904    const MTLS_CRL_PARTITION_B: &str = include_str!("../assets/mtls/crl-partition-b.pem");
4905
4906    /// The verdict of a `required` listener trusting the test CA with `crls`
4907    /// on the test client's certificate, at the current time.
4908    fn verify_mtls_client(crls: &[&str]) -> Result<(), rustls::Error> {
4909        verify_mtls_client_at(crls, rustls::pki_types::UnixTime::now())
4910    }
4911
4912    /// The same verdict, for a handshake at `time`.
4913    fn verify_mtls_client_at(
4914        crls: &[&str],
4915        time: rustls::pki_types::UnixTime,
4916    ) -> Result<(), rustls::Error> {
4917        let config =
4918            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4919        let verifier = HttpsListener::client_cert_verifier(&config, &test_crypto_provider())
4920            .expect("a CA and well-formed CRLs must build a verifier")
4921            .expect("required client auth must install a verifier");
4922        let client = CertificateDer::from_pem_slice(MTLS_CLIENT_PEM.as_bytes())
4923            .expect("the client fixture is a PEM certificate");
4924        verifier.verify_client_cert(&client, &[], time).map(|_| ())
4925    }
4926
4927    /// Whether a `required` listener can be built with `crls`, and the
4928    /// reason when it cannot.
4929    fn build_mtls_listener(crls: &[&str]) -> Result<(), String> {
4930        let config =
4931            https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4932        match HttpsListener::client_cert_verifier(&config, &test_crypto_provider()) {
4933            Ok(_) => Ok(()),
4934            Err(ListenerError::ClientAuth(reason)) => Err(reason),
4935            Err(other) => panic!("unexpected listener error {other:?}"),
4936        }
4937    }
4938
4939    #[test]
4940    fn client_revocation_accepts_a_client_no_crl_revokes() {
4941        assert_eq!(
4942            verify_mtls_client(&[]),
4943            Ok(()),
4944            "precondition: the client chains to the CA"
4945        );
4946        assert_eq!(
4947            verify_mtls_client(&[MTLS_CRL_CURRENT]),
4948            Ok(()),
4949            "a current CRL from the client's issuer that does not list it must not reject it"
4950        );
4951    }
4952
4953    #[test]
4954    fn client_revocation_rejects_a_revoked_client() {
4955        // The CRL is current, so only the serial lookup can reject the client.
4956        // Without `with_crls`, the client would be accepted.
4957        assert_eq!(
4958            verify_mtls_client(&[MTLS_CRL_REVOKED]),
4959            Err(rustls::Error::InvalidCertificate(
4960                rustls::CertificateError::Revoked
4961            ))
4962        );
4963    }
4964
4965    #[test]
4966    fn client_revocation_refuses_an_expired_crl_only_where_the_operator_states_it() {
4967        // The main process refuses a CRL already past its nextUpdate in what
4968        // an operator states: it would reject every client it covers.
4969        let now = i64::try_from(
4970            std::time::SystemTime::now()
4971                .duration_since(std::time::UNIX_EPOCH)
4972                .expect("a clock after 1970")
4973                .as_secs(),
4974        )
4975        .expect("a clock within i64");
4976        let refused = HttpsListener::check_crls_current(&[MTLS_CRL_EXPIRED.to_owned()], now);
4977        assert!(
4978            matches!(refused, Err(ListenerError::ClientAuth(ref reason)) if reason.contains("expired")),
4979            "an expired CRL must be refused when stated, got {refused:?}"
4980        );
4981        assert!(
4982            HttpsListener::check_crls_current(
4983                &[MTLS_CRL_CURRENT.to_owned(), MTLS_CRL_OTHER_CA.to_owned()],
4984                now
4985            )
4986            .is_ok()
4987        );
4988        // A worker still builds a listener whose CRL lapsed while it ran (a
4989        // restarted worker replaying the state), so a later reload has a
4990        // listener to patch; the lapsed CRL rejects clients at handshake.
4991        assert_eq!(build_mtls_listener(&[MTLS_CRL_EXPIRED]), Ok(()));
4992        let config = https_config_with_client_auth(
4993            ClientAuthMode::ClientAuthRequired,
4994            &[MTLS_CA_PEM],
4995            &[MTLS_CRL_EXPIRED],
4996        );
4997        assert!(HttpsListener::try_new(config, Token(0)).is_ok());
4998    }
4999
5000    #[test]
5001    fn client_revocation_rejects_a_client_once_its_crl_has_lapsed() {
5002        // A CRL current when the listener was built lapses while it runs.
5003        // In June 2125 the current CRL (nextUpdate January 2125) has lapsed
5004        // and the client certificate (notAfter 2126) has not, so only the
5005        // CRL expiry can reject the client. Without
5006        // `enforce_revocation_expiration`, rustls keeps trusting the stale CRL
5007        // and accepts it.
5008        let june_2125 =
5009            rustls::pki_types::UnixTime::since_unix_epoch(Duration::from_secs(4_905_100_800));
5010        let verdict = verify_mtls_client_at(&[MTLS_CRL_CURRENT], june_2125);
5011        assert!(
5012            matches!(
5013                verdict,
5014                Err(rustls::Error::InvalidCertificate(
5015                    rustls::CertificateError::ExpiredRevocationList
5016                        | rustls::CertificateError::ExpiredRevocationListContext { .. }
5017                ))
5018            ),
5019            "a CRL past its nextUpdate must reject the client, got {verdict:?}"
5020        );
5021        // Positive space: without CRLs the client is still valid then, so the
5022        // rejection above is the CRL expiry and nothing else.
5023        assert_eq!(verify_mtls_client_at(&[], june_2125), Ok(()));
5024    }
5025
5026    #[test]
5027    fn client_revocation_rejects_a_client_no_configured_crl_covers() {
5028        // Once any CRL is configured, a client whose issuer no CRL covers has
5029        // an unknown revocation status, which rustls's default
5030        // `UnknownStatusPolicy::Deny` rejects.
5031        assert_eq!(
5032            verify_mtls_client(&[MTLS_CRL_OTHER_CA]),
5033            Err(rustls::Error::InvalidCertificate(
5034                rustls::CertificateError::UnknownRevocationStatus
5035            ))
5036        );
5037        // Adding the issuer's own current CRL restores the verdict.
5038        assert_eq!(
5039            verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_CURRENT]),
5040            Ok(())
5041        );
5042    }
5043
5044    #[test]
5045    fn client_revocation_refuses_two_crls_of_one_issuer_with_overlapping_scopes() {
5046        // webpki would only read the first of them, so the order alone would
5047        // decide: listing the revoking CRL second accepts the revoked client.
5048        // Every order must fail the listener instead.
5049        for crls in [
5050            vec![MTLS_CRL_CURRENT, MTLS_CRL_REVOKED],
5051            vec![MTLS_CRL_REVOKED, MTLS_CRL_CURRENT],
5052            // A full CRL covers every partition of its CA.
5053            vec![MTLS_CRL_CURRENT, MTLS_CRL_PARTITION_A],
5054            vec![MTLS_CRL_PARTITION_A, MTLS_CRL_CURRENT],
5055            // The same partition twice.
5056            vec![MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_A],
5057        ] {
5058            let refused = build_mtls_listener(&crls);
5059            assert!(
5060                refused
5061                    .as_ref()
5062                    .is_err_and(|reason| reason.contains("overlapping")),
5063                "overlapping CRLs of one issuer must fail the listener, got {refused:?}"
5064            );
5065        }
5066        // Two CRLs in one configured entry count the same as two entries.
5067        assert!(build_mtls_listener(&[&format!("{MTLS_CRL_CURRENT}{MTLS_CRL_REVOKED}")]).is_err());
5068    }
5069
5070    #[test]
5071    fn client_revocation_takes_the_partitions_of_one_issuer() {
5072        // Partitions told apart by their distribution point cover disjoint
5073        // certificates, so a CA publishing its list that way stays usable.
5074        assert_eq!(
5075            build_mtls_listener(&[MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_B]),
5076            Ok(())
5077        );
5078    }
5079
5080    #[test]
5081    fn client_revocation_takes_one_crl_per_issuer() {
5082        // Positive space of the rule above: CRLs from distinct issuers are
5083        // all kept, and none shadows another.
5084        assert_eq!(
5085            verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_REVOKED]),
5086            Err(rustls::Error::InvalidCertificate(
5087                rustls::CertificateError::Revoked
5088            ))
5089        );
5090        assert_eq!(
5091            verify_mtls_client(&[MTLS_CRL_REVOKED, MTLS_CRL_OTHER_CA]),
5092            Err(rustls::Error::InvalidCertificate(
5093                rustls::CertificateError::Revoked
5094            ))
5095        );
5096    }
5097
5098    /// The in-place refresh of a running listener's client authentication
5099    /// policy: the patch replaces the policy and rebuilds the rustls context
5100    /// that holds the verifier; a policy the verifier cannot be built from
5101    /// leaves the listener exactly as it was. Its effect on handshakes is
5102    /// covered by `test_mtls_policy_patch_*` in `e2e/src/tests/tls_tests.rs`.
5103    #[test]
5104    fn a_client_auth_policy_patch_rebuilds_the_listener_verifier() {
5105        let config = https_config_with_client_auth(
5106            ClientAuthMode::ClientAuthRequired,
5107            &[MTLS_CA_PEM],
5108            &[MTLS_CRL_CURRENT],
5109        );
5110        let mut listener =
5111            HttpsListener::try_new(config.clone(), Token(0)).expect("build the mTLS listener");
5112        let original_context = listener.rustls_details.clone();
5113
5114        let refreshed = HttpsListenerConfig {
5115            client_ca_crls: vec![MTLS_CRL_REVOKED.to_owned()],
5116            ..config.clone()
5117        }
5118        .client_auth_policy();
5119        listener
5120            .update_config(&UpdateHttpsListenerConfig {
5121                address: config.address,
5122                client_auth_policy: Some(refreshed.clone()),
5123                ..Default::default()
5124            })
5125            .expect("a buildable policy must be applied");
5126        assert_eq!(listener.config.client_auth_policy(), refreshed);
5127        assert!(
5128            !Arc::ptr_eq(&listener.rustls_details, &original_context),
5129            "the verifier lives in the rustls context, which must be rebuilt"
5130        );
5131
5132        let applied_context = listener.rustls_details.clone();
5133        let unbuildable = ClientAuthPolicy {
5134            client_auth: Some(ClientAuthMode::ClientAuthRequired as i32),
5135            client_ca_certificates: vec![],
5136            client_ca_crls: vec![],
5137        };
5138        assert!(
5139            listener
5140                .update_config(&UpdateHttpsListenerConfig {
5141                    address: config.address,
5142                    client_auth_policy: Some(unbuildable.clone()),
5143                    ..Default::default()
5144                })
5145                .is_err(),
5146            "required client auth with no trusted CA must be refused"
5147        );
5148        assert!(HttpsListener::validate_client_auth_policy(&unbuildable).is_err());
5149        assert_eq!(
5150            listener.config.client_auth_policy(),
5151            refreshed,
5152            "a refused policy leaves the applied one in place"
5153        );
5154        assert!(Arc::ptr_eq(&listener.rustls_details, &applied_context));
5155    }
5156}