Skip to main content

sozu_lib/
https.rs

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