1use 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 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#[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
113macro_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
126macro_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
155fn upgraded_frontend_events(handshake_event: Ready, rustls_holds_input: bool) -> Ready {
186 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
195fn 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 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 None
274 } else {
275 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 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 #[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 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 #[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 None => {
391 debug_assert!(
395 self.state.failed(),
396 "a refused HTTPS upgrade must leave the state in FailedUpgrade"
397 );
398 #[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 handshake.frontend_readiness = readiness;
440 handshake.frontend_readiness.event.insert(Ready::READABLE);
441
442 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 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 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 let disable_http11 = self.listener.borrow().is_http11_disabled();
506 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 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 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 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 (AlpnProtocol::Http11, None) => true,
569 (AlpnProtocol::H2, None) => false,
570 },
571 "negotiated ALPN protocol and its wire label must agree"
572 );
573
574 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 let frontend_events = upgraded_frontend_events(
597 handshake.frontend_readiness.event,
598 !handshake.session.wants_read(),
599 );
600 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 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 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 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 context.tls_server_name = sni_owned;
738 context.tls_cert_names = tls_cert_names;
739 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 frontend.readiness_mut().event.insert(frontend_events);
786
787 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 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 timeouts: mux::MuxTimeouts::new(handshake.container_frontend_timeout),
816 backend_registry: mux::BackendRegistry::default(),
817 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 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 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 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 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 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 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 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 error!(
990 "{} upgrade called on WSS, this should not happen",
991 log_context!(self)
992 );
993 Some(HttpsStateMachine::WebSocket(wss))
994 }
995
996 #[cfg(debug_assertions)]
1002 fn check_invariants(&self) {
1003 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 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 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 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 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 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 self.state.close(self.proxy.clone(), &mut self.metrics);
1095
1096 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 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 let proxy = self.proxy.borrow();
1139 proxy.remove_session(self.frontend_token);
1140
1141 self.has_been_closed = true;
1142 debug_assert!(
1146 self.has_been_closed,
1147 "close() must leave the session marked closed"
1148 );
1149 }
1150
1151 fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
1152 let session_result = self.state.timeout(token, &mut self.metrics);
1153 if session_result == StateResult::CloseSession {
1154 debug!(
1155 "{} HTTPS timeout requested close: token={:?}, marker={:?}",
1156 log_context!(self),
1157 token,
1158 self.state.marker()
1159 );
1160 }
1161 session_result == StateResult::CloseSession
1162 }
1163
1164 fn needs_ready_pass(&self) -> bool {
1165 matches!(&self.state, HttpsStateMachine::Mux(mux) if mux.has_pending_backend_failure())
1166 }
1167
1168 fn protocol(&self) -> Protocol {
1169 Protocol::HTTPS
1170 }
1171
1172 fn update_readiness(&mut self, token: Token, events: Ready) {
1173 trace!(
1174 "{} token {:?} got event {}",
1175 log_context!(self),
1176 token,
1177 super::ready_to_string(events)
1178 );
1179 self.last_event = Instant::now();
1180 self.metrics.wait_start();
1181 if token == self.frontend_token {
1182 match &mut self.state {
1185 HttpsStateMachine::Mux(mux) => mux
1186 .frontend
1187 .socket_handler_mut()
1188 .readiness_delivered(events),
1189 HttpsStateMachine::WebSocket(pipe) => {
1190 pipe.front_socket_handler_mut().readiness_delivered(events)
1191 }
1192 HttpsStateMachine::Expect(..)
1194 | HttpsStateMachine::Handshake(_)
1195 | HttpsStateMachine::FailedUpgrade(_) => {}
1196 }
1197 }
1198 self.state.update_readiness(token, events);
1199 }
1200
1201 fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
1202 self.metrics.service_start();
1203
1204 let session_result =
1205 self.state
1206 .ready(session.clone(), self.proxy.clone(), &mut self.metrics);
1207
1208 let to_be_closed = match session_result {
1209 SessionResult::Close => true,
1210 SessionResult::Continue => false,
1211 SessionResult::Upgrade => match self.upgrade() {
1212 false => self.ready(session),
1213 true => true,
1214 },
1215 };
1216 if to_be_closed {
1217 debug!(
1218 "{} HTTPS ready requested close: marker={:?}",
1219 log_context!(self),
1220 self.state.marker()
1221 );
1222 }
1223
1224 #[cfg(debug_assertions)]
1228 self.check_invariants();
1229
1230 self.metrics.service_stop();
1231 to_be_closed
1232 }
1233
1234 fn shutting_down(&mut self) -> SessionIsToBeClosed {
1235 self.state.shutting_down()
1236 }
1237
1238 fn targets(&self, target: &crate::SessionTarget) -> bool {
1239 self.state.targets(target)
1240 }
1241
1242 fn last_event(&self) -> Instant {
1243 self.last_event
1244 }
1245
1246 fn print_session(&self) {
1247 self.state.print_state("HTTPS");
1248 error!("{} Metrics: {:?}", log_context!(self), self.metrics);
1249 }
1250
1251 fn frontend_token(&self) -> Token {
1252 self.frontend_token
1253 }
1254}
1255
1256pub type HostName = String;
1257pub type PathBegin = String;
1258
1259pub struct HttpsListener {
1260 active: bool,
1261 address: StdSocketAddr,
1262 answers: Rc<RefCell<HttpAnswers>>,
1263 config: HttpsListenerConfig,
1264 fronts: Router,
1265 listener: Option<MioTcpListener>,
1266 parked_listener: Option<MioTcpListener>,
1280 nodelay_backlog: bool,
1286 resolver: Arc<MutexCertificateResolver>,
1287 rustls_details: Arc<RustlsServerConfig>,
1288 tags: BTreeMap<String, CachedTags>,
1289 token: Token,
1290}
1291
1292impl ListenerHandler for HttpsListener {
1293 fn get_addr(&self) -> &StdSocketAddr {
1294 &self.address
1295 }
1296
1297 fn get_tags(&self, key: &str) -> Option<&CachedTags> {
1298 self.tags.get(key)
1299 }
1300
1301 fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
1302 match tags {
1303 Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
1304 None => self.tags.remove(&key),
1305 };
1306 }
1307
1308 fn protocol(&self) -> Protocol {
1309 Protocol::HTTPS
1310 }
1311
1312 fn public_address(&self) -> StdSocketAddr {
1313 self.config
1314 .public_address
1315 .map(|addr| addr.into())
1316 .unwrap_or(self.address)
1317 }
1318}
1319
1320impl L7ListenerHandler for HttpsListener {
1321 fn get_sticky_name(&self) -> &str {
1322 &self.config.sticky_name
1323 }
1324
1325 fn get_sozu_id_header(&self) -> &str {
1326 self.config
1327 .sozu_id_header
1328 .as_deref()
1329 .filter(|s| !s.is_empty())
1330 .unwrap_or("Sozu-Id")
1331 }
1332
1333 fn get_connect_timeout(&self) -> u32 {
1334 self.config.connect_timeout
1335 }
1336
1337 fn frontend_from_request(
1338 &self,
1339 host: &str,
1340 uri: &str,
1341 method: &Method,
1342 headers: &dyn RequestHeaders,
1343 ) -> Result<RouteResult, FrontendFromRequestError> {
1344 let start = Instant::now();
1345 let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
1346 Ok(tuple) => tuple,
1347 Err(parse_error) => {
1348 return Err(FrontendFromRequestError::HostParse {
1350 host: host.to_owned(),
1351 error: parse_error.to_string(),
1352 });
1353 }
1354 };
1355
1356 if remaining_input != &b""[..] {
1357 return Err(FrontendFromRequestError::InvalidCharsAfterHost(
1358 host.to_owned(),
1359 ));
1360 }
1361
1362 let host = unsafe { from_utf8_unchecked(hostname) };
1371
1372 let route = self
1373 .fronts
1374 .lookup_with_headers(host, uri, method, headers)
1375 .map_err(|e| {
1376 incr!(names::http::FAILED_BACKEND_MATCHING);
1377 FrontendFromRequestError::NoClusterFound(e)
1378 })?;
1379
1380 let now = Instant::now();
1381
1382 if let Some(cluster) = route.cluster_id.as_deref() {
1383 time!(
1384 names::event_loop::FRONTEND_MATCHING_TIME,
1385 cluster,
1386 (now - start).as_millis()
1387 );
1388 }
1389
1390 Ok(route)
1391 }
1392
1393 fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
1394 &self.answers
1395 }
1396
1397 fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
1398 crate::protocol::mux::H2FloodConfig::from_optional(
1406 self.config.h2_max_rst_stream_per_window,
1407 self.config.h2_max_ping_per_window,
1408 self.config.h2_max_settings_per_window,
1409 self.config.h2_max_empty_data_per_window,
1410 self.config.h2_max_window_update_stream0_per_window,
1411 self.config.h2_max_continuation_frames,
1412 self.config.h2_max_glitch_count,
1413 self.config.h2_max_rst_stream_lifetime,
1414 self.config.h2_max_rst_stream_abusive_lifetime,
1415 self.config.h2_max_rst_stream_emitted_lifetime,
1416 self.config.h2_max_header_list_size,
1417 self.config.h2_max_header_table_size,
1418 self.config.h2_max_header_fields,
1419 self.config.h2_stream_refusal_percent,
1420 )
1421 }
1422
1423 fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1424 crate::protocol::mux::H2ConnectionConfig::from_optional(
1425 self.config.h2_initial_connection_window,
1426 self.config.h2_max_concurrent_streams,
1427 self.config.h2_stream_shrink_ratio,
1428 )
1429 }
1430
1431 fn get_strict_sni_binding(&self) -> bool {
1432 self.config.strict_sni_binding.unwrap_or(true)
1447 }
1448
1449 fn get_elide_x_real_ip(&self) -> bool {
1450 self.config.elide_x_real_ip.unwrap_or(false)
1451 }
1452
1453 fn get_send_x_real_ip(&self) -> bool {
1454 self.config.send_x_real_ip.unwrap_or(false)
1455 }
1456
1457 fn get_forwarded_headers(&self) -> ForwardedHeaders {
1458 self.config.forwarded_headers()
1461 }
1462
1463 fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1464 let seconds = self
1471 .config
1472 .h2_stream_idle_timeout_seconds
1473 .map(|s| u64::from(s.max(1)))
1474 .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1475 std::time::Duration::from_secs(seconds)
1476 }
1477
1478 fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1479 match self.config.h2_graceful_shutdown_deadline_seconds {
1480 None => Some(std::time::Duration::from_secs(5)),
1481 Some(0) => None,
1482 Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1483 }
1484 }
1485}
1486
1487impl HttpsListener {
1488 pub fn key(&self) -> ListenerKey {
1491 ListenerKey::new(self.address, self.config.interface.as_deref())
1492 }
1493
1494 pub fn is(&self, key: &ListenerKey) -> bool {
1496 key.matches(&self.address, self.config.interface.as_deref())
1497 }
1498
1499 pub fn is_http11_disabled(&self) -> bool {
1505 self.config.disable_http11.unwrap_or(false)
1506 }
1507
1508 pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1514 &self.resolver
1515 }
1516
1517 pub fn try_new(
1518 config: HttpsListenerConfig,
1519 token: Token,
1520 ) -> Result<HttpsListener, ListenerError> {
1521 if let Some(v) = config.forwarded_headers {
1524 validate_forwarded_headers(v)?;
1525 }
1526 let resolver = Arc::new(MutexCertificateResolver::default());
1527
1528 let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1529
1530 let answers = Self::build_answers(&config)?;
1531
1532 Ok(HttpsListener {
1533 listener: None,
1534 parked_listener: None,
1535 nodelay_backlog: false,
1536 address: config.address.into(),
1537 resolver,
1538 rustls_details: server_config,
1539 active: false,
1540 fronts: Router::new(),
1541 answers: Rc::new(RefCell::new(answers)),
1542 config,
1543 token,
1544 tags: BTreeMap::new(),
1545 })
1546 }
1547
1548 fn build_answers(config: &HttpsListenerConfig) -> Result<HttpAnswers, ListenerError> {
1555 let mut answers_map = config.answers.clone();
1556 if let Some(ref legacy) = config.http_answers {
1557 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1558 }
1559 HttpAnswers::new(&answers_map)
1560 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1561 }
1562
1563 pub fn validate_config(config: &HttpsListenerConfig) -> Result<(), ListenerError> {
1574 Self::create_rustls_context(config, Arc::new(MutexCertificateResolver::default()))?;
1584 Self::build_answers(config)?;
1585 Ok(())
1586 }
1587
1588 pub fn activate(
1589 &mut self,
1590 registry: &Registry,
1591 tcp_listener: Option<MioTcpListener>,
1592 ) -> Result<Token, ListenerError> {
1593 if self.active {
1594 return Ok(self.token);
1595 }
1596 let address: StdSocketAddr = self.config.address.into();
1597
1598 let parked = self.parked_listener.take();
1604 let mut listener = match (tcp_listener, parked) {
1605 (Some(inherited), Some(parked)) => {
1606 warn!(
1614 "{} closing the socket parked on the HTTPS listener for {}: an \
1615 inherited socket arrived for the same address and is the one being \
1616 activated",
1617 log_module_context!(),
1618 address
1619 );
1620 drop(parked);
1621 inherited
1622 }
1623 (Some(inherited), None) => inherited,
1624 (None, Some(parked)) => parked,
1625 (None, None) => server_bind(address, self.config.interface.as_deref()).map_err(
1626 |server_bind_error| ListenerError::Activation {
1627 address,
1628 error: server_bind_error.to_string(),
1629 },
1630 )?,
1631 };
1632
1633 if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
1637 error!(
1638 "{} error setting nodelay on listen socket({:?}): {:?}",
1639 log_module_context!(),
1640 listener,
1641 e
1642 );
1643 }
1644
1645 let registration = registry
1646 .register(&mut listener, self.token, Interest::READABLE)
1647 .map_err(ListenerError::SocketRegistration);
1648 if let Err(error) = registration {
1649 self.parked_listener = Some(listener);
1659 return Err(error);
1660 }
1661
1662 self.listener = Some(listener);
1663 self.nodelay_backlog = true;
1664 self.active = true;
1665 debug_assert!(
1671 self.active && self.listener.is_some(),
1672 "an activated HTTPS listener must hold a bound socket and be flagged active"
1673 );
1674 Ok(self.token)
1675 }
1676
1677 pub fn create_rustls_context(
1678 config: &HttpsListenerConfig,
1679 resolver: Arc<MutexCertificateResolver>,
1680 ) -> Result<RustlsServerConfig, ListenerError> {
1681 let cipher_names = if config.cipher_list.is_empty() {
1682 DEFAULT_CIPHER_LIST.to_vec()
1683 } else {
1684 config
1685 .cipher_list
1686 .iter()
1687 .map(|s| s.as_str())
1688 .collect::<Vec<_>>()
1689 };
1690
1691 let ciphers = cipher_names
1692 .into_iter()
1693 .filter_map(|cipher| {
1694 cipher_suite_by_name(cipher).or_else(|| {
1695 error!(
1696 "{} unknown or unsupported cipher: {:?}",
1697 log_module_context!(),
1698 cipher
1699 );
1700 None
1701 })
1702 })
1703 .collect::<Vec<_>>();
1704
1705 let versions = config
1706 .versions
1707 .iter()
1708 .filter_map(|version| match TlsVersion::try_from(*version) {
1709 Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1710 Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1711 Ok(other_version) => {
1712 error!(
1713 "{} unsupported TLS version {:?}",
1714 log_module_context!(),
1715 other_version
1716 );
1717 None
1718 }
1719 Err(_) => {
1720 error!("{} unsupported TLS version", log_module_context!());
1721 None
1722 }
1723 })
1724 .collect::<Vec<_>>();
1725
1726 let kx_groups = if config.groups_list.is_empty() {
1727 default_provider().kx_groups
1728 } else {
1729 config
1730 .groups_list
1731 .iter()
1732 .filter_map(|group| match kx_group_by_name(group) {
1733 Some(kx) => Some(kx),
1734 None => {
1735 debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1736 None
1737 }
1738 })
1739 .collect::<Vec<_>>()
1740 };
1741
1742 let provider = Arc::new(CryptoProvider {
1743 cipher_suites: ciphers,
1744 kx_groups,
1745 ..default_provider()
1746 });
1747
1748 let builder = RustlsServerConfig::builder_with_provider(provider.clone())
1749 .with_protocol_versions(&versions[..])
1750 .map_err(|err| ListenerError::BuildRustls(err.to_string()))?;
1751
1752 let builder = match Self::client_cert_verifier(config, &provider)? {
1762 Some(verifier) => builder.with_client_cert_verifier(verifier),
1763 None => builder.with_no_client_auth(),
1764 };
1765
1766 let mut server_config = builder.with_cert_resolver(resolver);
1767 server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1768
1769 server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1770 DEFAULT_ALPN_PROTOCOLS
1771 .iter()
1772 .map(|p| p.as_bytes().to_vec())
1773 .collect()
1774 } else {
1775 config
1776 .alpn_protocols
1777 .iter()
1778 .map(|p| p.as_bytes().to_vec())
1779 .collect()
1780 };
1781
1782 Ok(server_config)
1783 }
1784
1785 fn check_crl_set(crls: &[CertificateRevocationListDer<'_>]) -> Result<(), ListenerError> {
1798 let mut scopes: Vec<(Vec<u8>, Option<Vec<u8>>)> = Vec::with_capacity(crls.len());
1799 for (index, crl) in crls.iter().enumerate() {
1800 let parsed = webpki::BorrowedCertRevocationList::from_der(crl.as_ref())
1801 .map_err(|e| ListenerError::ClientAuth(format!("invalid CRL: {e}")))?;
1802 let parsed = webpki::CertRevocationList::from(parsed);
1803 let issuer = parsed.issuer().to_vec();
1804 let distribution_point = parsed.issuing_distribution_point().map(<[u8]>::to_vec);
1805 let overlapping = scopes.iter().position(|(seen_issuer, seen_point)| {
1806 *seen_issuer == issuer
1807 && (seen_point.is_none()
1808 || distribution_point.is_none()
1809 || *seen_point == distribution_point)
1810 });
1811 if let Some(first) = overlapping {
1812 return Err(ListenerError::ClientAuth(format!(
1813 "CRLs #{} and #{} (counted across client_ca_crls) are issued by the \
1814 same CA for overlapping certificates, and only the first would be \
1815 consulted: configure one CRL per issuer, or one per distribution point",
1816 first + 1,
1817 index + 1
1818 )));
1819 }
1820 scopes.push((issuer, distribution_point));
1821 }
1822 Ok(())
1823 }
1824
1825 pub fn check_crls_current(crls: &[String], now: i64) -> Result<(), ListenerError> {
1837 let mut index = 0;
1838 for pem in crls {
1839 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1840 let crl =
1841 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?;
1842 index += 1;
1843 if let Some(next_update) = sozu_command::certificate::crl_next_update(crl.as_ref())
1844 && next_update <= now
1845 {
1846 return Err(ListenerError::ClientAuth(format!(
1847 "CRL #{index} (counted across client_ca_crls) expired {} seconds ago: \
1848 its nextUpdate is past, refresh it",
1849 now - next_update
1850 )));
1851 }
1852 }
1853 }
1854 Ok(())
1855 }
1856
1857 pub fn validate_client_auth_policy(policy: &ClientAuthPolicy) -> Result<(), ListenerError> {
1863 let mut config = HttpsListenerConfig::default();
1864 config.apply_client_auth_policy(policy);
1865 Self::client_cert_verifier(&config, &Arc::new(default_provider())).map(|_| ())
1866 }
1867
1868 fn client_cert_verifier(
1884 config: &HttpsListenerConfig,
1885 provider: &Arc<CryptoProvider>,
1886 ) -> Result<Option<Arc<dyn rustls::server::danger::ClientCertVerifier>>, ListenerError> {
1887 let raw = config
1889 .client_auth
1890 .unwrap_or(ClientAuthMode::ClientAuthNone as i32);
1891 let mode = ClientAuthMode::try_from(raw).map_err(|_| {
1892 ListenerError::ClientAuth(format!("unknown client_auth mode value {raw}"))
1893 })?;
1894 if mode == ClientAuthMode::ClientAuthNone {
1895 return Ok(None);
1896 }
1897
1898 let mut roots = RootCertStore::empty();
1905 for pem in &config.client_ca_certificates {
1906 let before = roots.len();
1907 for cert in CertificateDer::pem_slice_iter(pem.as_bytes()) {
1908 let cert =
1909 cert.map_err(|e| ListenerError::ClientAuth(format!("invalid CA PEM: {e}")))?;
1910 roots
1911 .add(cert)
1912 .map_err(|e| ListenerError::ClientAuth(format!("untrusted CA: {e}")))?;
1913 }
1914 if roots.len() == before {
1915 return Err(ListenerError::ClientAuth(
1916 "a configured trusted-CA entry contained no certificate".to_owned(),
1917 ));
1918 }
1919 }
1920 if roots.is_empty() {
1921 return Err(ListenerError::ClientAuth(
1922 "client auth requested but no trusted CA certificate was provided".to_owned(),
1923 ));
1924 }
1925
1926 let mut crls = Vec::new();
1931 for pem in &config.client_ca_crls {
1932 let before = crls.len();
1933 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1934 crls.push(
1935 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?,
1936 );
1937 }
1938 if crls.len() == before {
1939 return Err(ListenerError::ClientAuth(
1940 "a configured CRL entry contained no certificate revocation list".to_owned(),
1941 ));
1942 }
1943 }
1944 Self::check_crl_set(&crls)?;
1945
1946 let mut builder =
1947 WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone());
1948 if !crls.is_empty() {
1949 builder = builder.with_crls(crls).enforce_revocation_expiration();
1954 }
1955 if mode == ClientAuthMode::ClientAuthOptional {
1956 builder = builder.allow_unauthenticated();
1957 }
1958 let verifier = builder
1959 .build()
1960 .map_err(|e| ListenerError::ClientAuth(e.to_string()))?;
1961 Ok(Some(verifier))
1962 }
1963
1964 pub fn update_config(
1980 &mut self,
1981 patch: &UpdateHttpsListenerConfig,
1982 ) -> Result<(), ListenerError> {
1983 validate_h2_flood_knobs_https(patch)?;
1991 if let Some(ref alpn) = patch.alpn_protocols {
1992 validate_alpn_protocols(&alpn.values)?;
1993 }
1994 if let Some(ref hdr) = patch.sozu_id_header {
1995 validate_sozu_id_header(hdr)?;
1996 }
1997 if let Some(v) = patch.forwarded_headers {
1998 validate_forwarded_headers(v)?;
1999 }
2000 if patch.hsts.is_some_and(|hsts| hsts.enabled.is_none()) {
2007 return Err(ListenerError::HstsEnabledRequired);
2008 }
2009 HttpAnswers::validate_patch_templates(patch.http_answers.as_ref(), &patch.answers)
2010 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
2011
2012 let mut config = self.config.clone();
2013
2014 if let Some(v) = patch.public_address {
2016 config.public_address = Some(v);
2017 }
2018 if let Some(v) = patch.expect_proxy {
2019 config.expect_proxy = v;
2020 }
2021 if let Some(ref v) = patch.sticky_name {
2022 config.sticky_name = v.to_owned();
2023 }
2024 if let Some(v) = patch.front_timeout {
2025 config.front_timeout = v;
2026 }
2027 if let Some(v) = patch.back_timeout {
2028 config.back_timeout = v;
2029 }
2030 if let Some(v) = patch.connect_timeout {
2031 config.connect_timeout = v;
2032 }
2033 if let Some(v) = patch.request_timeout {
2034 config.request_timeout = v;
2035 }
2036 if let Some(v) = patch.strict_sni_binding {
2037 config.strict_sni_binding = Some(v);
2038 }
2039 if let Some(v) = patch.disable_http11 {
2040 config.disable_http11 = Some(v);
2041 }
2042 if let Some(ref v) = patch.sozu_id_header {
2043 config.sozu_id_header = Some(v.to_owned());
2044 }
2045 if let Some(v) = patch.elide_x_real_ip {
2046 config.elide_x_real_ip = Some(v);
2047 }
2048 if let Some(v) = patch.send_x_real_ip {
2049 config.send_x_real_ip = Some(v);
2050 }
2051 if let Some(v) = patch.forwarded_headers {
2052 config.forwarded_headers = Some(v);
2053 }
2054
2055 if let Some(v) = patch.h2_max_rst_stream_per_window {
2057 config.h2_max_rst_stream_per_window = Some(v);
2058 }
2059 if let Some(v) = patch.h2_max_ping_per_window {
2060 config.h2_max_ping_per_window = Some(v);
2061 }
2062 if let Some(v) = patch.h2_max_settings_per_window {
2063 config.h2_max_settings_per_window = Some(v);
2064 }
2065 if let Some(v) = patch.h2_max_empty_data_per_window {
2066 config.h2_max_empty_data_per_window = Some(v);
2067 }
2068 if let Some(v) = patch.h2_max_continuation_frames {
2069 config.h2_max_continuation_frames = Some(v);
2070 }
2071 if let Some(v) = patch.h2_max_glitch_count {
2072 config.h2_max_glitch_count = Some(v);
2073 }
2074 if let Some(v) = patch.h2_initial_connection_window {
2075 config.h2_initial_connection_window = Some(v);
2076 }
2077 if let Some(v) = patch.h2_max_concurrent_streams {
2078 config.h2_max_concurrent_streams = Some(v);
2079 }
2080 if let Some(v) = patch.h2_stream_shrink_ratio {
2081 config.h2_stream_shrink_ratio = Some(v);
2082 }
2083 if let Some(v) = patch.h2_max_rst_stream_lifetime {
2084 config.h2_max_rst_stream_lifetime = Some(v);
2085 }
2086 if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
2087 config.h2_max_rst_stream_abusive_lifetime = Some(v);
2088 }
2089 if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
2090 config.h2_max_rst_stream_emitted_lifetime = Some(v);
2091 }
2092 if let Some(v) = patch.h2_max_header_list_size {
2093 config.h2_max_header_list_size = Some(v);
2094 }
2095 if let Some(v) = patch.h2_max_header_table_size {
2096 config.h2_max_header_table_size = Some(v);
2097 }
2098 if let Some(v) = patch.h2_max_header_fields {
2099 config.h2_max_header_fields = Some(v);
2100 }
2101 if let Some(v) = patch.h2_stream_refusal_percent {
2102 config.h2_stream_refusal_percent = Some(v);
2103 }
2104 if let Some(v) = patch.h2_stream_idle_timeout_seconds {
2105 config.h2_stream_idle_timeout_seconds = Some(v);
2106 }
2107 if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
2108 config.h2_graceful_shutdown_deadline_seconds = Some(v);
2109 }
2110 if let Some(v) = patch.h2_max_window_update_stream0_per_window {
2111 config.h2_max_window_update_stream0_per_window = Some(v);
2112 }
2113
2114 if let Some(new_hsts) = patch.hsts {
2115 config.hsts = Some(new_hsts);
2116 }
2117 if let Some(ref alpn_wrapper) = patch.alpn_protocols {
2118 config.alpn_protocols = alpn_wrapper.values.clone();
2120 }
2121 if let Some(ref policy) = patch.client_auth_policy {
2122 config.apply_client_auth_policy(policy);
2123 }
2124
2125 let new_rustls = if patch.alpn_protocols.is_some() || patch.client_auth_policy.is_some() {
2137 Some(Arc::new(Self::create_rustls_context(
2138 &config,
2139 self.resolver.clone(),
2140 )?))
2141 } else {
2142 None
2143 };
2144
2145 let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
2150 let rebuilt = if answers_changed {
2151 if let Some(ref new_answers) = patch.http_answers {
2152 crate::sozu_command::state::merge_custom_http_answers(
2153 &mut config.http_answers,
2154 new_answers,
2155 );
2156 }
2157 for (code, body) in &patch.answers {
2158 if !body.is_empty() {
2159 config.answers.insert(code.clone(), body.clone());
2160 }
2161 }
2162
2163 let mut answers_map = config.answers.clone();
2164 if let Some(ref legacy) = config.http_answers {
2165 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
2166 }
2167 Some(
2168 HttpAnswers::new(&answers_map)
2169 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?,
2170 )
2171 } else {
2172 None
2173 };
2174
2175 self.config = config;
2177 if let Some(new_rustls) = new_rustls {
2178 self.rustls_details = new_rustls;
2179 }
2180 if let Some(alpn_wrapper) = &patch.alpn_protocols {
2181 debug_assert_eq!(
2186 self.config.alpn_protocols, alpn_wrapper.values,
2187 "committed ALPN config must match the patch values exactly"
2188 );
2189 }
2190 if let Some(mut rebuilt) = rebuilt {
2191 rebuilt.cluster_answers = self.answers.borrow().cluster_answers.clone();
2206 self.answers = Rc::new(RefCell::new(rebuilt));
2207 }
2208
2209 if patch.hsts.is_some() {
2226 let refreshed = self
2227 .fronts
2228 .refresh_inheriting_hsts(self.config.hsts.as_ref());
2229 for _ in 0..refreshed {
2230 crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
2231 }
2232 info!(
2233 "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
2234 frontend(s). Explicit per-frontend overrides untouched.",
2235 log_module_context!(),
2236 self.config.address,
2237 refreshed,
2238 );
2239 crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
2240 }
2241
2242 Ok(())
2243 }
2244
2245 pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2246 self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
2247 }
2248
2249 pub fn add_https_front_with_hsts_origin(
2256 &mut self,
2257 tls_front: HttpFrontend,
2258 hsts_origin: crate::router::HstsOrigin,
2259 ) -> Result<(), ListenerError> {
2260 self.fronts
2261 .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
2262 .map_err(ListenerError::AddFrontend)
2263 }
2264
2265 pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2266 debug!(
2267 "{} removing tls_front {:?}",
2268 log_module_context!(),
2269 tls_front
2270 );
2271 self.fronts
2272 .remove_http_front(&tls_front)
2273 .map_err(ListenerError::RemoveFrontend)
2274 }
2275
2276 fn accept(&mut self) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2277 if let Some(ref sock) = self.listener {
2278 match sock.accept() {
2279 Ok((frontend_sock, peer)) => {
2280 if self.nodelay_backlog
2281 && let Err(e) = frontend_sock.set_nodelay(true)
2282 {
2283 error!(
2284 "{} error setting nodelay on front socket({:?}): {:?}",
2285 log_module_context!(),
2286 frontend_sock,
2287 e
2288 );
2289 }
2290 Ok((frontend_sock, peer))
2291 }
2292 Err(e) if e.kind() == ErrorKind::WouldBlock => {
2293 self.nodelay_backlog = false;
2295 Err(AcceptError::WouldBlock)
2296 }
2297 Err(e) => {
2298 error!("{} accept() IO error: {:?}", log_module_context!(), e);
2299 Err(AcceptError::IoError)
2300 }
2301 }
2302 } else {
2303 error!(
2304 "{} cannot accept connections, no listening socket available",
2305 log_module_context!()
2306 );
2307 Err(AcceptError::IoError)
2308 }
2309 }
2310}
2311
2312pub struct HttpsProxy {
2313 listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
2314 clusters: HashMap<ClusterId, Cluster>,
2315 backends: Rc<RefCell<BackendMap>>,
2316 pool: Rc<RefCell<Pool>>,
2317 registry: Registry,
2318 sessions: Rc<RefCell<SessionManager>>,
2319}
2320
2321impl HttpsProxy {
2322 pub fn new(
2323 registry: Registry,
2324 sessions: Rc<RefCell<SessionManager>>,
2325 pool: Rc<RefCell<Pool>>,
2326 backends: Rc<RefCell<BackendMap>>,
2327 ) -> HttpsProxy {
2328 HttpsProxy {
2329 listeners: HashMap::new(),
2330 clusters: HashMap::new(),
2331 backends,
2332 pool,
2333 registry,
2334 sessions,
2335 }
2336 }
2337
2338 pub fn add_listener(
2339 &mut self,
2340 config: HttpsListenerConfig,
2341 token: Token,
2342 ) -> Result<Token, ProxyError> {
2343 if self.listeners.contains_key(&token) {
2344 return Err(ProxyError::ListenerAlreadyPresent);
2345 }
2346 let mut https_listener =
2347 HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
2348 if let Some(sibling) = self
2356 .listeners_at(https_listener.address)
2357 .ok()
2358 .and_then(|listeners| listeners.into_iter().next())
2359 {
2360 let sibling = sibling.borrow();
2361 https_listener.fronts = sibling.fronts.clone();
2362 https_listener
2366 .fronts
2367 .refresh_inheriting_hsts(https_listener.config.hsts.as_ref());
2368 https_listener.tags = sibling.tags.clone();
2369 let certificates = sibling
2370 .resolver
2371 .0
2372 .lock()
2373 .map_err(|e| ProxyError::Lock(e.to_string()))?
2374 .clone();
2375 *https_listener
2376 .resolver
2377 .0
2378 .lock()
2379 .map_err(|e| ProxyError::Lock(e.to_string()))? = certificates;
2380 }
2381 match self.listeners.entry(token) {
2382 Entry::Vacant(entry) => {
2383 entry.insert(Rc::new(RefCell::new(https_listener)));
2384 Ok(token)
2385 }
2386 _ => Err(ProxyError::ListenerAlreadyPresent),
2387 }
2388 }
2389
2390 pub(crate) fn add_listener_with_retained_state(
2398 &mut self,
2399 config: HttpsListenerConfig,
2400 token: Token,
2401 retained_frontends: Vec<RequestHttpFrontend>,
2402 retained_certificates: Vec<AddCertificate>,
2403 ) -> Result<Token, ProxyError> {
2404 let address = config.address.into();
2405 let had_sibling = self.listeners_at(address).is_ok();
2406 self.add_listener(config, token)?;
2407
2408 if !had_sibling {
2409 for frontend in retained_frontends {
2410 if let Err(error) = self.add_https_frontend(frontend) {
2411 self.listeners.remove(&token);
2412 return Err(error);
2413 }
2414 }
2415 for certificate in retained_certificates {
2416 if let Err(error) = self.add_certificate(certificate) {
2417 self.listeners.remove(&token);
2418 return Err(error);
2419 }
2420 }
2421 }
2422
2423 Ok(token)
2424 }
2425
2426 pub fn remove_listener(
2427 &mut self,
2428 remove: RemoveListener,
2429 ) -> Result<Option<ResponseContent>, ProxyError> {
2430 let len = self.listeners.len();
2431
2432 let remove_address = remove.listener_key();
2433 self.listeners
2434 .retain(|_, listener| !listener.borrow().is(&remove_address));
2435
2436 if !self.listeners.len() < len {
2437 info!(
2438 "{} no HTTPS listener to remove at address {}",
2439 log_module_context!(),
2440 remove_address
2441 )
2442 }
2443 Ok(None)
2444 }
2445
2446 pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
2447 let listeners = std::mem::take(&mut self.listeners);
2448 let mut socket_errors = vec![];
2449 for l in listeners.values() {
2450 if let Some(mut sock) = l.borrow_mut().listener.take() {
2451 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2452 if let Err(e) = self.registry.deregister(&mut sock) {
2453 let error = format!("socket {sock:?}: {e:?}");
2454 socket_errors.push(error);
2455 }
2456 }
2457 }
2458
2459 if !socket_errors.is_empty() {
2460 return Err(ProxyError::SoftStop {
2461 proxy_protocol: "HTTPS".to_string(),
2462 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2463 });
2464 }
2465
2466 Ok(())
2467 }
2468
2469 pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
2470 let mut listeners = std::mem::take(&mut self.listeners);
2471 let mut socket_errors = vec![];
2472 for (_, l) in listeners.drain() {
2473 if let Some(mut sock) = l.borrow_mut().listener.take() {
2474 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2475 if let Err(e) = self.registry.deregister(&mut sock) {
2476 let error = format!("socket {sock:?}: {e:?}");
2477 socket_errors.push(error);
2478 }
2479 }
2480 }
2481
2482 if !socket_errors.is_empty() {
2483 return Err(ProxyError::HardStop {
2484 proxy_protocol: "HTTPS".to_string(),
2485 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2486 });
2487 }
2488
2489 Ok(())
2490 }
2491
2492 pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
2493 let certificates = self
2494 .listeners
2495 .values()
2496 .map(|listener| {
2497 let owned = listener.borrow();
2498 let resolver = owned
2503 .resolver
2504 .0
2505 .lock()
2506 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2507 let certificate_summaries = resolver
2508 .domains
2509 .to_hashmap()
2510 .drain()
2511 .map(|(k, fingerprint)| CertificateSummary {
2512 domain: certificate_summary_domain(&k),
2513 fingerprint: fingerprint.to_string(),
2514 })
2515 .collect();
2516
2517 Ok(CertificatesByAddress {
2518 address: owned.address.into(),
2519 certificate_summaries,
2520 })
2521 })
2522 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2523
2524 let listeners_count = certificates.len();
2525 let certificates_count = certificates
2526 .iter()
2527 .map(|entry| entry.certificate_summaries.len())
2528 .fold(0usize, |total, count| total.saturating_add(count));
2529
2530 info!(
2531 "{} got Certificates::All query, listeners_count={} certificates_count={}",
2532 log_module_context!(),
2533 listeners_count,
2534 certificates_count,
2535 );
2536
2537 Ok(Some(
2538 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2539 ))
2540 }
2541
2542 pub fn query_certificate_for_domain(
2543 &mut self,
2544 domain: String,
2545 ) -> Result<Option<ResponseContent>, ProxyError> {
2546 let domain_key = domain.to_ascii_lowercase();
2553 let certificates = self
2554 .listeners
2555 .values()
2556 .map(|listener| {
2557 let owned = listener.borrow();
2558 let resolver = owned
2561 .resolver
2562 .0
2563 .lock()
2564 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2565 let mut certificate_summaries = vec![];
2566
2567 if let Some((k, fingerprint)) = resolver.domain_lookup(domain_key.as_bytes(), true)
2568 {
2569 certificate_summaries.push(CertificateSummary {
2570 domain: certificate_summary_domain(k),
2571 fingerprint: fingerprint.to_string(),
2572 });
2573 }
2574 Ok(CertificatesByAddress {
2575 address: owned.address.into(),
2576 certificate_summaries,
2577 })
2578 })
2579 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2580
2581 let listeners_count = certificates.len();
2582 let certificates_count = certificates
2583 .iter()
2584 .map(|entry| entry.certificate_summaries.len())
2585 .fold(0usize, |total, count| total.saturating_add(count));
2586
2587 info!(
2588 "{} got Certificates::Domain query, domain_bytes={} listeners_count={} certificates_count={}",
2589 log_module_context!(),
2590 domain.len(),
2591 listeners_count,
2592 certificates_count,
2593 );
2594
2595 Ok(Some(
2596 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2597 ))
2598 }
2599
2600 pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2608 use crate::InheritedSocketFate;
2609 match self
2610 .listeners
2611 .values()
2612 .find(|listener| listener.borrow().is(addr))
2613 {
2614 None => InheritedSocketFate::Unclaimed,
2615 Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2616 Some(_) => InheritedSocketFate::Adopted,
2617 }
2618 }
2619
2620 pub fn activate_listener(
2621 &mut self,
2622 addr: &ListenerKey,
2623 tcp_listener: Option<MioTcpListener>,
2624 ) -> Result<Token, ProxyError> {
2625 let listener = self
2626 .listeners
2627 .values()
2628 .find(|listener| listener.borrow().is(addr))
2629 .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2630
2631 listener
2632 .borrow_mut()
2633 .activate(&self.registry, tcp_listener)
2634 .map_err(|listener_error| ProxyError::ListenerActivation {
2635 address: addr.address,
2636 listener_error,
2637 })
2638 }
2639
2640 pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
2649 self.listeners
2650 .iter()
2651 .find(|(_, listener)| listener.borrow().is(address))
2652 .map(|(token, _)| *token)
2653 }
2654
2655 pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
2656 self.listeners
2657 .values()
2658 .filter_map(|listener| {
2659 let mut owned = listener.borrow_mut();
2660 if let Some(listener) = owned.listener.take() {
2661 owned.active = false;
2664 return Some((owned.key(), listener));
2665 }
2666
2667 None
2668 })
2669 .collect()
2670 }
2671
2672 pub fn give_back_listener(
2673 &mut self,
2674 address: &ListenerKey,
2675 ) -> Result<(Token, MioTcpListener), ProxyError> {
2676 let listener = self
2677 .listeners
2678 .values()
2679 .find(|listener| listener.borrow().is(address))
2680 .ok_or(ProxyError::NoListenerFound(address.clone()))?;
2681
2682 let mut owned = listener.borrow_mut();
2683
2684 let taken_listener = owned
2685 .listener
2686 .take()
2687 .ok_or(ProxyError::UnactivatedListener)?;
2688
2689 owned.active = false;
2692
2693 Ok((owned.token, taken_listener))
2694 }
2695
2696 fn listeners_at(
2700 &self,
2701 address: StdSocketAddr,
2702 ) -> Result<Vec<Rc<RefCell<HttpsListener>>>, ProxyError> {
2703 let mut listeners: Vec<_> = self
2704 .listeners
2705 .iter()
2706 .filter(|(_, listener)| listener.borrow().address == address)
2707 .map(|(token, listener)| (*token, listener.clone()))
2708 .collect();
2709 listeners.sort_by_key(|(token, _)| *token);
2711 let listeners: Vec<_> = listeners
2712 .into_iter()
2713 .map(|(_, listener)| listener)
2714 .collect();
2715 if listeners.is_empty() {
2716 return Err(ProxyError::NoListenerFound(address.into()));
2717 }
2718 Ok(listeners)
2719 }
2720
2721 pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
2723 let address: std::net::SocketAddr = patch.address.into();
2724 let listener = self
2725 .listeners
2726 .values()
2727 .find(|l| l.borrow().address == address)
2728 .ok_or(ProxyError::NoListenerFound(address.into()))?;
2729 listener
2730 .borrow_mut()
2731 .update_config(&patch)
2732 .map_err(|listener_error| ProxyError::ListenerActivation {
2733 address,
2734 listener_error,
2735 })
2736 }
2737
2738 pub fn add_cluster(
2739 &mut self,
2740 mut cluster: Cluster,
2741 ) -> Result<Option<ResponseContent>, ProxyError> {
2742 let mut cluster_overrides = cluster.answers.clone();
2743 if let Some(answer_503) = cluster.answer_503.take() {
2744 cluster_overrides
2745 .entry("503".to_owned())
2746 .or_insert(answer_503);
2747 }
2748 if !cluster_overrides.is_empty() {
2749 for listener in self.listeners.values() {
2750 listener
2751 .borrow()
2752 .answers
2753 .borrow_mut()
2754 .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
2755 .map_err(|(status, error)| {
2756 ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
2757 })?;
2758 }
2759 }
2760 self.clusters
2761 .insert(cluster.cluster_id.as_str().into(), cluster);
2762 Ok(None)
2763 }
2764
2765 pub fn remove_cluster(
2766 &mut self,
2767 cluster_id: &str,
2768 ) -> Result<Option<ResponseContent>, ProxyError> {
2769 self.clusters.remove(cluster_id);
2770 for listener in self.listeners.values() {
2771 listener
2772 .borrow()
2773 .answers
2774 .borrow_mut()
2775 .remove_cluster_answers(cluster_id);
2776 }
2777
2778 Ok(None)
2779 }
2780
2781 pub fn add_https_frontend(
2782 &mut self,
2783 front: RequestHttpFrontend,
2784 ) -> Result<Option<ResponseContent>, ProxyError> {
2785 let front = front.clone().to_frontend().map_err(|request_error| {
2786 ProxyError::WrongInputFrontend {
2787 front: Box::new(front),
2788 error: request_error.to_string(),
2789 }
2790 })?;
2791
2792 for listener in self.listeners_at(front.address)? {
2793 let mut listener = listener.borrow_mut();
2794 let mut front = front.clone();
2795
2796 let hsts_origin = if front.hsts.is_none() {
2813 front.hsts = listener.config.hsts;
2814 crate::router::HstsOrigin::InheritedFromListenerDefault
2815 } else {
2816 crate::router::HstsOrigin::Explicit
2817 };
2818
2819 listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
2820 listener
2821 .add_https_front_with_hsts_origin(front, hsts_origin)
2822 .map_err(ProxyError::AddFrontend)?;
2823 }
2824 Ok(None)
2825 }
2826
2827 pub fn remove_https_frontend(
2828 &mut self,
2829 front: RequestHttpFrontend,
2830 ) -> Result<Option<ResponseContent>, ProxyError> {
2831 let front = front.clone().to_frontend().map_err(|request_error| {
2832 ProxyError::WrongInputFrontend {
2833 front: Box::new(front),
2834 error: request_error.to_string(),
2835 }
2836 })?;
2837
2838 let mut result = Ok(None);
2842 for listener in self.listeners_at(front.address)? {
2843 let mut listener = listener.borrow_mut();
2844 let hostname = front.hostname.to_owned();
2845
2846 if let Err(error) = listener.remove_https_front(front.clone())
2847 && result.is_ok()
2848 {
2849 result = Err(ProxyError::RemoveFrontend(error));
2850 }
2851
2852 if !listener.fronts.has_hostname(&hostname) {
2853 listener.set_tags(hostname, None);
2854 }
2855 }
2856 result
2857 }
2858
2859 pub fn add_certificate(
2860 &mut self,
2861 add_certificate: AddCertificate,
2862 ) -> Result<Option<ResponseContent>, ProxyError> {
2863 let address = add_certificate.address.into();
2864
2865 for listener in self.listeners_at(address)? {
2866 let listener = listener.borrow();
2867 let mut resolver = listener
2868 .resolver
2869 .0
2870 .lock()
2871 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2872
2873 resolver
2874 .add_certificate(&add_certificate)
2875 .map_err(ProxyError::AddCertificate)?;
2876 }
2877
2878 Ok(None)
2879 }
2880
2881 pub fn remove_certificate(
2883 &mut self,
2884 remove_certificate: RemoveCertificate,
2885 ) -> Result<Option<ResponseContent>, ProxyError> {
2886 let address = remove_certificate.address.into();
2887
2888 let fingerprint = Fingerprint(
2889 hex::decode(&remove_certificate.fingerprint)
2890 .map_err(ProxyError::WrongCertificateFingerprint)?,
2891 );
2892
2893 let mut result = Ok(None);
2897 for listener in self.listeners_at(address)? {
2898 let listener = listener.borrow();
2899 let applied = listener
2900 .resolver
2901 .0
2902 .lock()
2903 .map_err(|e| ProxyError::Lock(e.to_string()))
2904 .and_then(|mut resolver| {
2905 resolver
2906 .remove_certificate(&fingerprint)
2907 .map_err(ProxyError::RemoveCertificate)
2908 });
2909 if let Err(error) = applied
2910 && result.is_ok()
2911 {
2912 result = Err(error);
2913 }
2914 }
2915 result
2916 }
2917
2918 pub fn replace_certificate(
2920 &mut self,
2921 replace_certificate: ReplaceCertificate,
2922 ) -> Result<Option<ResponseContent>, ProxyError> {
2923 let address = replace_certificate.address.into();
2924
2925 let mut result = Ok(None);
2929 for listener in self.listeners_at(address)? {
2930 let listener = listener.borrow();
2931 let applied = listener
2932 .resolver
2933 .0
2934 .lock()
2935 .map_err(|e| ProxyError::Lock(e.to_string()))
2936 .and_then(|mut resolver| {
2937 resolver
2938 .replace_certificate(&replace_certificate)
2939 .map_err(ProxyError::ReplaceCertificate)
2940 });
2941 if let Err(error) = applied
2942 && result.is_ok()
2943 {
2944 result = Err(error);
2945 }
2946 }
2947 result
2948 }
2949}
2950
2951impl ProxyConfiguration for HttpsProxy {
2952 fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2953 match self.listeners.get(&Token(token.0)) {
2954 Some(listener) => listener.borrow_mut().accept(),
2955 None => Err(AcceptError::IoError),
2956 }
2957 }
2958
2959 fn create_session(
2960 &mut self,
2961 mut frontend_sock: MioTcpStream,
2962 peer: StdSocketAddr,
2963 token: ListenToken,
2964 wait_time: Duration,
2965 proxy: Rc<RefCell<Self>>,
2966 ) -> Result<(), AcceptError> {
2967 let listener = self
2968 .listeners
2969 .get(&Token(token.0))
2970 .ok_or(AcceptError::IoError)?;
2971 let owned = listener.borrow();
2976 let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
2977 error!(
2978 "{} failed to create server session: {:?}",
2979 log_module_context!(),
2980 e
2981 );
2982 AcceptError::IoError
2983 })?;
2984
2985 let mut session_manager = self.sessions.borrow_mut();
2986 let entry = session_manager.slab.vacant_entry();
2987 let session_token = Token(entry.key());
2988 debug_assert_eq!(
2993 session_token.0,
2994 entry.key(),
2995 "HTTPS session token must equal its slab key"
2996 );
2997
2998 self.registry
2999 .register(
3000 &mut frontend_sock,
3001 session_token,
3002 Interest::READABLE | Interest::WRITABLE,
3003 )
3004 .map_err(|register_error| {
3005 error!(
3006 "{} error registering front socket({:?}): {:?}",
3007 log_module_context!(),
3008 frontend_sock,
3009 register_error
3010 );
3011 AcceptError::RegisterError
3012 })?;
3013
3014 let public_address: StdSocketAddr = match owned.config.public_address {
3015 Some(pub_addr) => pub_addr.into(),
3016 None => owned.config.address.into(),
3017 };
3018
3019 let session = Rc::new(RefCell::new(HttpsSession::new(
3020 Duration::from_secs(owned.config.back_timeout as u64),
3021 Duration::from_secs(owned.config.connect_timeout as u64),
3022 Duration::from_secs(owned.config.front_timeout as u64),
3023 Duration::from_secs(owned.config.request_timeout as u64),
3024 owned.config.expect_proxy,
3025 listener.clone(),
3026 Rc::downgrade(&self.pool),
3027 proxy,
3028 public_address,
3029 rustls_details,
3030 frontend_sock,
3031 peer,
3032 session_token,
3033 wait_time,
3034 )));
3035 debug_assert_eq!(
3039 session.borrow().frontend_token(),
3040 session_token,
3041 "stored HTTPS session must report the slab token as its frontend token"
3042 );
3043 entry.insert(session);
3044
3045 Ok(())
3046 }
3047
3048 fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
3049 let request_id = request.id.clone();
3050
3051 let request_type = match request.content.request_type {
3052 Some(t) => t,
3053 None => return WorkerResponse::error(request_id, "Empty request"),
3054 };
3055
3056 let content_result = match request_type {
3057 RequestType::AddCluster(cluster) => {
3058 debug!(
3059 "{} {} add cluster {:?}",
3060 log_module_context!(),
3061 request_id,
3062 cluster
3063 );
3064 self.add_cluster(cluster)
3065 }
3066 RequestType::RemoveCluster(cluster_id) => {
3067 debug!(
3068 "{} {} remove cluster {:?}",
3069 log_module_context!(),
3070 request_id,
3071 cluster_id
3072 );
3073 self.remove_cluster(&cluster_id)
3074 }
3075 RequestType::AddHttpsFrontend(front)
3076 | RequestType::AddHttpsFrontendWithSilentClose(front)
3077 | RequestType::AddHttpsFrontendWithHeaderPredicates(front) => {
3078 debug!(
3079 "{} {} add https front {:?}",
3080 log_module_context!(),
3081 request_id,
3082 front
3083 );
3084 self.add_https_frontend(front)
3085 }
3086 RequestType::RemoveHttpsFrontend(front)
3087 | RequestType::RemoveHttpsFrontendWithHeaderPredicates(front) => {
3088 debug!(
3089 "{} {} remove https front {:?}",
3090 log_module_context!(),
3091 request_id,
3092 front
3093 );
3094 self.remove_https_frontend(front)
3095 }
3096 RequestType::AddCertificate(add_certificate) => {
3097 debug!(
3098 "{} {} add certificate: {:?}",
3099 log_module_context!(),
3100 request_id,
3101 add_certificate
3102 );
3103 self.add_certificate(add_certificate)
3104 }
3105 RequestType::RemoveCertificate(remove_certificate) => {
3106 debug!(
3107 "{} {} remove certificate: {:?}",
3108 log_module_context!(),
3109 request_id,
3110 remove_certificate
3111 );
3112 self.remove_certificate(remove_certificate)
3113 }
3114 RequestType::ReplaceCertificate(replace_certificate) => {
3115 debug!(
3116 "{} {} replace certificate: {:?}",
3117 log_module_context!(),
3118 request_id,
3119 replace_certificate
3120 );
3121 self.replace_certificate(replace_certificate)
3122 }
3123 RequestType::RemoveListener(remove) => {
3124 debug!(
3125 "{} removing HTTPS listener at address {:?}",
3126 log_module_context!(),
3127 remove.address
3128 );
3129 self.remove_listener(remove)
3130 }
3131 RequestType::SoftStop(_) => {
3132 debug!(
3133 "{} {} processing soft shutdown",
3134 log_module_context!(),
3135 request_id
3136 );
3137 match self.soft_stop() {
3138 Ok(_) => {
3139 info!(
3140 "{} {} soft stop successful",
3141 log_module_context!(),
3142 request_id
3143 );
3144 return WorkerResponse::processing(request.id);
3145 }
3146 Err(e) => Err(e),
3147 }
3148 }
3149 RequestType::HardStop(_) => {
3150 debug!(
3151 "{} {} processing hard shutdown",
3152 log_module_context!(),
3153 request_id
3154 );
3155 match self.hard_stop() {
3156 Ok(_) => {
3157 debug!(
3158 "{} {} hard stop successful",
3159 log_module_context!(),
3160 request_id
3161 );
3162 return WorkerResponse::processing(request.id);
3163 }
3164 Err(e) => Err(e),
3165 }
3166 }
3167 RequestType::Status(_) => {
3168 debug!("{} {} status", log_module_context!(), request_id);
3169 Ok(None)
3170 }
3171 RequestType::QueryCertificatesFromWorkers(filters) => {
3172 if let Some(domain) = filters.domain {
3173 debug!(
3174 "{} {} query certificate for domain_bytes={}",
3175 log_module_context!(),
3176 request_id,
3177 domain.len(),
3178 );
3179 self.query_certificate_for_domain(domain)
3180 } else {
3181 debug!(
3182 "{} {} query all certificates",
3183 log_module_context!(),
3184 request_id
3185 );
3186 self.query_all_certificates()
3187 }
3188 }
3189 other_request => {
3190 debug!(
3191 "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
3192 log_module_context!(),
3193 request.id,
3194 other_request
3195 );
3196 Err(ProxyError::UnsupportedMessage)
3197 }
3198 };
3199
3200 match content_result {
3201 Ok(content) => {
3202 debug!("{} {} successful", log_module_context!(), request_id);
3203 match content {
3204 Some(content) => WorkerResponse::ok_with_content(request_id, content),
3205 None => WorkerResponse::ok(request_id),
3206 }
3207 }
3208 Err(proxy_error) => {
3209 debug!(
3210 "{} {} unsuccessful: {}",
3211 log_module_context!(),
3212 request_id,
3213 proxy_error
3214 );
3215 WorkerResponse::error(request_id, proxy_error)
3216 }
3217 }
3218 }
3219}
3220impl L7Proxy for HttpsProxy {
3221 fn kind(&self) -> ListenerType {
3222 ListenerType::Https
3223 }
3224
3225 fn register_socket(
3226 &self,
3227 socket: &mut MioTcpStream,
3228 token: Token,
3229 interest: Interest,
3230 ) -> Result<(), std::io::Error> {
3231 self.registry.register(socket, token, interest)
3232 }
3233
3234 fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
3235 let mut session_manager = self.sessions.borrow_mut();
3236 let entry = session_manager.slab.vacant_entry();
3237 let token = Token(entry.key());
3238 let _entry = entry.insert(session);
3239 token
3240 }
3241
3242 fn remove_session(&self, token: Token) -> bool {
3243 let mut sessions = self.sessions.borrow_mut();
3244 sessions.untrack_all_cluster_ip(token);
3247 sessions.slab.try_remove(token.0).is_some()
3248 }
3249
3250 fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
3251 &self.clusters
3252 }
3253
3254 fn sessions(&self) -> Rc<RefCell<SessionManager>> {
3255 self.sessions.clone()
3256 }
3257}
3258
3259fn rustls_version_str(version: ProtocolVersion) -> &'static str {
3261 match version {
3262 ProtocolVersion::SSLv2 => "tls.version.SSLv2",
3263 ProtocolVersion::SSLv3 => "tls.version.SSLv3",
3264 ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
3265 ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
3266 ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
3267 ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
3268 ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
3269 ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
3270 ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
3271 ProtocolVersion::Unknown(_) => "tls.version.Unknown",
3272 _ => "tls.version.unimplemented",
3273 }
3274}
3275
3276pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
3283 match version {
3284 ProtocolVersion::SSLv2 => Some("SSLv2"),
3285 ProtocolVersion::SSLv3 => Some("SSLv3"),
3286 ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
3287 ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
3288 ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
3289 ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
3290 ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
3291 ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
3292 ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
3293 _ => None,
3294 }
3295}
3296
3297fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
3299 match cipher.suite() {
3300 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3301 "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
3302 }
3303 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3304 "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
3305 }
3306 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3307 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
3308 }
3309 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3310 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
3311 }
3312 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3313 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
3314 }
3315 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3316 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
3317 }
3318 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
3319 CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
3320 CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
3321 _ => "tls.cipher.Unsupported",
3322 }
3323}
3324
3325pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
3332 match cipher.suite() {
3333 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3334 Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
3335 }
3336 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3337 Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
3338 }
3339 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3340 Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
3341 }
3342 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3343 Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
3344 }
3345 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3346 Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
3347 }
3348 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3349 Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
3350 }
3351 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
3352 CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
3353 CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
3354 _ => None,
3355 }
3356}
3357
3358pub mod testing {
3359 use crate::testing::*;
3360
3361 pub fn start_https_worker(
3363 config: HttpsListenerConfig,
3364 channel: ProxyChannel,
3365 max_buffers: usize,
3366 buffer_size: usize,
3367 ) -> anyhow::Result<()> {
3368 let address = config.address.into();
3369
3370 let ServerParts {
3371 event_loop,
3372 registry,
3373 sessions,
3374 pool,
3375 backends,
3376 client_scm_socket: _,
3377 server_scm_socket,
3378 server_config,
3379 } = prebuild_server(max_buffers, buffer_size, true)?;
3380
3381 let token = {
3382 let mut sessions = sessions.borrow_mut();
3383 let entry = sessions.slab.vacant_entry();
3384 let key = entry.key();
3385 let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3386 protocol: Protocol::HTTPSListen,
3387 })));
3388 Token(key)
3389 };
3390
3391 let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3392 proxy
3393 .add_listener(config, token)
3394 .with_context(|| "Failed at creating adding the listener")?;
3395 proxy
3396 .activate_listener(&address, None)
3397 .with_context(|| "Failed at creating activating the listener")?;
3398
3399 let mut server = Server::new(
3400 event_loop,
3401 channel,
3402 server_scm_socket,
3403 sessions,
3404 pool,
3405 backends,
3406 None,
3407 Some(proxy),
3408 None,
3409 server_config,
3410 None,
3411 false,
3412 )
3413 .with_context(|| "Failed at creating server")?;
3414
3415 debug!("{} starting event loop", log_module_context!());
3416 server.run();
3417 debug!("{} ending event loop", log_module_context!());
3418 Ok(())
3419 }
3420}
3421
3422#[cfg(test)]
3423mod tests {
3424 use std::sync::Arc;
3425
3426 use sozu_command::{
3427 config::ListenerBuilder,
3428 proto::command::{CertificateAndKey, SocketAddress},
3429 };
3430
3431 use super::*;
3432 use crate::{
3433 router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode},
3434 socket::SocketHandler,
3435 };
3436
3437 #[test]
3442 fn the_upgraded_frontend_reads_only_when_a_read_is_due() {
3443 assert_eq!(
3444 upgraded_frontend_events(Ready::EMPTY, false),
3445 Ready::WRITABLE,
3446 "a drained socket and an empty rustls need no read"
3447 );
3448 assert_eq!(
3449 upgraded_frontend_events(Ready::WRITABLE, false),
3450 Ready::WRITABLE
3451 );
3452 assert_eq!(
3453 upgraded_frontend_events(Ready::READABLE, false),
3454 Ready::READABLE | Ready::WRITABLE,
3455 "an edge not yet read must survive the upgrade"
3456 );
3457 assert_eq!(
3458 upgraded_frontend_events(Ready::EMPTY, true),
3459 Ready::READABLE | Ready::WRITABLE,
3460 "plaintext rustls already holds raises no edge"
3461 );
3462 assert_eq!(
3463 upgraded_frontend_events(Ready::READABLE | Ready::HUP, true),
3464 Ready::READABLE | Ready::WRITABLE | Ready::HUP,
3465 "a half-close seen during the handshake must reach the mux"
3466 );
3467 assert_eq!(
3468 upgraded_frontend_events(Ready::HUP, false),
3469 Ready::WRITABLE | Ready::HUP,
3470 "HUP survives even when no read is due"
3471 );
3472 assert_eq!(
3473 upgraded_frontend_events(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED, true),
3474 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::WRITE_CLOSED,
3475 "a hang-up seen during the handshake must reach the mux as one"
3476 );
3477 }
3478
3479 #[test]
3480 fn successful_tls_handshake_log_bounds_sni_and_alpn() {
3481 const SNI_SECRET: &str = "HTTPS_HANDSHAKE_SNI_SECRET_SENTINEL";
3482 const ALPN_SECRET: &str = "HTTPS_HANDSHAKE_ALPN_SECRET_SENTINEL";
3483
3484 let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
3485 let alpn = format!("{ALPN_SECRET}{}", "x".repeat(4096));
3486 let sni_len = sni.len();
3487 let alpn_len = alpn.len();
3488 let output = successful_tls_handshake_summary(Some(&sni), Some(&alpn));
3489
3490 for secret in [SNI_SECRET, ALPN_SECRET] {
3491 assert!(
3492 !output.contains(secret),
3493 "successful TLS handshake log leaked {secret}: {output}"
3494 );
3495 }
3496 for metadata in [
3497 format!("sni_bytes=Some({sni_len})"),
3498 format!("alpn_bytes=Some({alpn_len})"),
3499 ] {
3500 assert!(
3501 output.contains(&metadata),
3502 "successful TLS handshake log omitted {metadata}: {output}"
3503 );
3504 }
3505 assert!(
3506 output.len() <= 512,
3507 "successful TLS handshake log is not bounded: {} bytes",
3508 output.len()
3509 );
3510 }
3511
3512 fn proxy_with_certificate_domain(domain: String) -> HttpsProxy {
3513 let crate::testing::ServerParts {
3514 registry,
3515 sessions,
3516 pool,
3517 backends,
3518 ..
3519 } = crate::testing::prebuild_server(16, 16_384, false)
3520 .expect("test HTTPS proxy dependencies must initialize");
3521 let address = SocketAddress::new_v4(127, 0, 0, 1, crate::testing::provide_port());
3522 let mut config = ListenerBuilder::new_https(address)
3523 .to_tls(None)
3524 .expect("test HTTPS listener config must build");
3525 config.cipher_list = vec!["TLS13_AES_256_GCM_SHA384".to_owned()];
3526 config.groups_list = vec!["P-256".to_owned()];
3527 let mut proxy = HttpsProxy::new(registry, sessions, pool, backends);
3528 proxy
3529 .add_listener(config, Token(10))
3530 .expect("test HTTPS listener must be added");
3531 proxy
3532 .add_certificate(AddCertificate {
3533 address,
3534 certificate: CertificateAndKey {
3535 certificate: include_str!("../assets/certificate.pem").to_owned(),
3536 key: include_str!("../assets/key.pem").to_owned(),
3537 certificate_chain: Vec::new(),
3538 versions: Vec::new(),
3539 names: vec![domain],
3540 },
3541 expired_at: None,
3542 })
3543 .expect("test certificate must be added");
3544 proxy
3545 }
3546
3547 #[test]
3561 fn a_poisoned_resolver_fails_the_certificate_queries_instead_of_the_worker() {
3562 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3563
3564 let resolver = proxy
3565 .listeners
3566 .values()
3567 .next()
3568 .expect("the test proxy has one listener")
3569 .borrow()
3570 .resolver()
3571 .clone();
3572
3573 let poisoner = resolver.clone();
3577 let unwound = std::thread::spawn(move || {
3578 let _guard = poisoner.0.lock().expect("first lock is not poisoned");
3579 panic!("simulated worker panic while holding the resolver lock");
3580 })
3581 .join();
3582 assert!(unwound.is_err(), "the poisoning thread must have unwound");
3583 assert!(
3584 resolver.0.lock().is_err(),
3585 "the resolver mutex must now be poisoned"
3586 );
3587
3588 assert!(
3589 matches!(proxy.query_all_certificates(), Err(ProxyError::Lock(_))),
3590 "Certificates::All on a poisoned resolver must report a lock error"
3591 );
3592 assert!(
3593 matches!(
3594 proxy.query_certificate_for_domain("lolcatho.st".to_owned()),
3595 Err(ProxyError::Lock(_))
3596 ),
3597 "Certificates::Domain on a poisoned resolver must report a lock error"
3598 );
3599 }
3600
3601 #[test]
3616 fn a_non_utf8_resolver_key_is_listed_lossily_not_panicked() {
3617 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3618
3619 let resolver = proxy
3620 .listeners
3621 .values()
3622 .next()
3623 .expect("the test proxy has one listener")
3624 .borrow()
3625 .resolver()
3626 .clone();
3627 {
3628 let mut owned = resolver.0.lock().expect("resolver lock");
3629 let inserted = owned
3630 .domains
3631 .insert(vec![0xff, 0xfe], Fingerprint(vec![0x01, 0x02]));
3632 assert_ne!(
3633 inserted,
3634 crate::router::pattern_trie::InsertResult::Failed,
3635 "the SNI trie must accept the non-UTF-8 key this test relies on"
3636 );
3637 }
3638
3639 let response = proxy
3640 .query_all_certificates()
3641 .expect("a non-UTF-8 trie key must not fail the query");
3642 assert!(
3643 response.is_some(),
3644 "Certificates::All must still answer with a listing"
3645 );
3646 }
3647
3648 #[test]
3649 fn query_all_certificates_log_redacts_response_domains() {
3650 const DOMAIN_SECRET: &str = "QUERY_ALL_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3651
3652 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3656 let output = crate::capture_test_logs(move || {
3657 let mut proxy = proxy_with_certificate_domain(domain);
3658 proxy
3659 .query_all_certificates()
3660 .expect("certificate query must succeed");
3661 });
3662
3663 assert!(
3664 !output.contains(DOMAIN_SECRET),
3665 "Certificates::All log leaked response domain {DOMAIN_SECRET}"
3666 );
3667 for metadata in ["listeners_count=1", "certificates_count=1"] {
3668 assert!(
3669 output.contains(metadata),
3670 "Certificates::All log omitted bounded metadata {metadata}: {output}"
3671 );
3672 }
3673 assert!(
3674 output.len() <= 1024,
3675 "Certificates::All log capture is not bounded: {} bytes",
3676 output.len()
3677 );
3678 }
3679
3680 #[test]
3681 fn query_certificate_for_domain_log_redacts_request_and_response_domain() {
3682 const DOMAIN_SECRET: &str = "QUERY_ONE_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3683
3684 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3687 let domain_len = domain.len();
3688 let output = crate::capture_test_logs_at_level("debug", move || {
3689 let mut proxy = proxy_with_certificate_domain(domain.clone());
3690 let response = proxy.notify(WorkerRequest {
3691 id: "query-domain-redaction-test".to_owned(),
3692 content: RequestType::QueryCertificatesFromWorkers(
3693 sozu_command::proto::command::QueryCertificatesFilters {
3694 domain: Some(domain),
3695 fingerprint: None,
3696 },
3697 )
3698 .into(),
3699 });
3700 assert_eq!(
3701 response.status,
3702 sozu_command::proto::command::ResponseStatus::Ok as i32,
3703 "domain certificate query must succeed: {}",
3704 response.message
3705 );
3706 });
3707
3708 assert!(
3709 !output.contains(DOMAIN_SECRET),
3710 "Certificates::Domain log leaked request or response domain {DOMAIN_SECRET}"
3711 );
3712 for metadata in [
3713 format!("domain_bytes={domain_len}"),
3714 "listeners_count=1".to_owned(),
3715 "certificates_count=1".to_owned(),
3716 ] {
3717 assert!(
3718 output.contains(&metadata),
3719 "Certificates::Domain log omitted bounded metadata {metadata}: {output}"
3720 );
3721 }
3722 assert!(
3723 output.len() <= 1024,
3724 "Certificates::Domain log capture is not bounded: {} bytes",
3725 output.len()
3726 );
3727 }
3728
3729 #[test]
3730 fn frontend_from_request_test() {
3731 let cluster_id1 = ClusterId::from("cluster_1");
3732 let cluster_id2 = ClusterId::from("cluster_2");
3733 let cluster_id3 = ClusterId::from("cluster_3");
3734 let uri1 = "/".to_owned();
3735 let uri2 = "/yolo".to_owned();
3736 let uri3 = "/yolo/swag".to_owned();
3737
3738 let mut fronts = Router::new();
3739 assert!(fronts.add_tree_rule(
3740 "lolcatho.st".as_bytes(),
3741 &PathRule::Prefix(uri1),
3742 &MethodRule::new(None),
3743 &Route::ClusterId(cluster_id1.clone())
3744 ));
3745 assert!(fronts.add_tree_rule(
3746 "lolcatho.st".as_bytes(),
3747 &PathRule::Prefix(uri2),
3748 &MethodRule::new(None),
3749 &Route::ClusterId(cluster_id2)
3750 ));
3751 assert!(fronts.add_tree_rule(
3752 "lolcatho.st".as_bytes(),
3753 &PathRule::Prefix(uri3),
3754 &MethodRule::new(None),
3755 &Route::ClusterId(cluster_id3)
3756 ));
3757 assert!(fronts.add_tree_rule(
3758 "other.domain".as_bytes(),
3759 &PathRule::Prefix("test".to_string()),
3760 &MethodRule::new(None),
3761 &Route::ClusterId(cluster_id1)
3762 ));
3763
3764 let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
3765 let resolver = Arc::new(MutexCertificateResolver::default());
3766
3767 let crypto_provider = Arc::new(default_provider());
3768
3769 let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
3770 .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
3771 .expect("could not create rustls config server")
3772 .with_no_client_auth()
3773 .with_cert_resolver(resolver.clone());
3774
3775 let rustls_details = Arc::new(server_config);
3776
3777 let default_config = ListenerBuilder::new_https(address)
3778 .to_tls(None)
3779 .expect("Could not create default HTTPS listener config");
3780
3781 println!("it doesn't even matter");
3782
3783 let listener = HttpsListener {
3784 parked_listener: None,
3785 nodelay_backlog: false,
3786 listener: None,
3787 address: address.into(),
3788 fronts,
3789 rustls_details,
3790 resolver,
3791 answers: Rc::new(RefCell::new(
3792 HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
3793 )),
3794 config: default_config,
3795 token: Token(0),
3796 active: true,
3797 tags: BTreeMap::new(),
3798 };
3799
3800 println!("TEST {}", line!());
3801 let frontend1 = listener.frontend_from_request(
3802 "lolcatho.st",
3803 "/",
3804 &Method::Get,
3805 &crate::router::NoHeaders,
3806 );
3807 assert_eq!(
3808 frontend1
3809 .expect("should find a frontend")
3810 .cluster_id
3811 .as_deref(),
3812 Some("cluster_1")
3813 );
3814 println!("TEST {}", line!());
3815 let frontend2 = listener.frontend_from_request(
3816 "lolcatho.st",
3817 "/test",
3818 &Method::Get,
3819 &crate::router::NoHeaders,
3820 );
3821 assert_eq!(
3822 frontend2
3823 .expect("should find a frontend")
3824 .cluster_id
3825 .as_deref(),
3826 Some("cluster_1")
3827 );
3828 println!("TEST {}", line!());
3829 let frontend3 = listener.frontend_from_request(
3830 "lolcatho.st",
3831 "/yolo/test",
3832 &Method::Get,
3833 &crate::router::NoHeaders,
3834 );
3835 assert_eq!(
3836 frontend3
3837 .expect("should find a frontend")
3838 .cluster_id
3839 .as_deref(),
3840 Some("cluster_2")
3841 );
3842 println!("TEST {}", line!());
3843 let frontend4 = listener.frontend_from_request(
3844 "lolcatho.st",
3845 "/yolo/swag",
3846 &Method::Get,
3847 &crate::router::NoHeaders,
3848 );
3849 assert_eq!(
3850 frontend4
3851 .expect("should find a frontend")
3852 .cluster_id
3853 .as_deref(),
3854 Some("cluster_3")
3855 );
3856 println!("TEST {}", line!());
3857 let frontend5 =
3858 listener.frontend_from_request("domain", "/", &Method::Get, &crate::router::NoHeaders);
3859 assert!(frontend5.is_err());
3860 }
3862
3863 #[test]
3864 fn wildcard_certificate_names() {
3865 let mut trie = TrieNode::root();
3866
3867 trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
3868 trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
3869 trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
3870 trie.domain_insert(
3871 "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
3872 3u8,
3873 );
3874 trie.domain_insert(
3875 "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
3876 4u8,
3877 );
3878
3879 let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
3880 println!("query result: {res:?}");
3881
3882 assert_eq!(
3883 trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
3884 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3885 );
3886 assert_eq!(
3887 trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
3888 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3889 );
3890 }
3891
3892 #[test]
3893 fn wildcard_with_subdomains() {
3894 let mut trie = TrieNode::root();
3895
3896 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3897 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3898
3899 let res = trie.domain_lookup(b"sub.test.example.com", true);
3900 println!("query result: {res:?}");
3901
3902 assert_eq!(
3903 trie.domain_lookup(b"sub.test.example.com", true),
3904 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3905 );
3906 assert_eq!(
3907 trie.domain_lookup(b"hello.sub.test.example.com", true),
3908 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3909 );
3910
3911 let mut trie = TrieNode::root();
3913
3914 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3915 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3916
3917 let res = trie.domain_lookup(b"sub.test.example.com", true);
3918 println!("query result: {res:?}");
3919
3920 assert_eq!(
3921 trie.domain_lookup(b"sub.test.example.com", true),
3922 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3923 );
3924 assert_eq!(
3925 trie.domain_lookup(b"hello.sub.test.example.com", true),
3926 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3927 );
3928 }
3929
3930 #[test]
3933 fn a_listener_with_an_unknown_forwarded_headers_value_is_refused() {
3934 let build = |forwarded_headers: Option<i32>| {
3935 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1043))
3936 .to_tls(None)
3937 .expect("default HTTPS listener config");
3938 cfg.forwarded_headers = forwarded_headers;
3939 HttpsListener::try_new(cfg, Token(0))
3940 };
3941 assert!(build(Some(-1)).is_err(), "an unknown mode must be refused");
3942 let listener = build(Some(ForwardedHeaders::None as i32)).expect("a known mode");
3943 assert_eq!(listener.get_forwarded_headers(), ForwardedHeaders::None);
3944 }
3945
3946 #[test]
3952 fn get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold() {
3953 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1042))
3954 .to_tls(None)
3955 .expect("default HTTPS listener config");
3956 cfg.h2_max_rst_stream_per_window = Some(101);
3957 cfg.h2_max_ping_per_window = Some(102);
3958 cfg.h2_max_settings_per_window = Some(103);
3959 cfg.h2_max_empty_data_per_window = Some(104);
3960 cfg.h2_max_window_update_stream0_per_window = Some(105);
3961 cfg.h2_max_continuation_frames = Some(106);
3962 cfg.h2_max_glitch_count = Some(107);
3963 cfg.h2_max_rst_stream_lifetime = Some(108);
3964 cfg.h2_max_rst_stream_abusive_lifetime = Some(109);
3965 cfg.h2_max_rst_stream_emitted_lifetime = Some(110);
3966 cfg.h2_max_header_list_size = Some(111);
3967 cfg.h2_max_header_table_size = Some(112);
3968 cfg.h2_max_header_fields = Some(113);
3969 cfg.h2_stream_refusal_percent = Some(114);
3970
3971 let listener = HttpsListener::try_new(cfg, Token(0)).expect("build listener");
3972 let flood = listener.get_h2_flood_config();
3973
3974 assert_eq!(flood.max_rst_stream_per_window(), 101);
3975 assert_eq!(flood.max_ping_per_window(), 102);
3976 assert_eq!(flood.max_settings_per_window(), 103);
3977 assert_eq!(flood.max_empty_data_per_window(), 104);
3978 assert_eq!(flood.max_window_update_stream0_per_window(), 105);
3979 assert_eq!(flood.max_continuation_frames(), 106);
3980 assert_eq!(flood.max_glitch_count(), 107);
3981 assert_eq!(flood.max_rst_stream_lifetime(), 108);
3982 assert_eq!(flood.max_rst_stream_abusive_lifetime(), 109);
3983 assert_eq!(flood.max_rst_stream_emitted_lifetime(), 110);
3984 assert_eq!(flood.max_header_list_size(), 111);
3985 assert_eq!(flood.max_header_table_size(), 112);
3986 assert_eq!(flood.max_header_fields(), 113);
3987 assert_eq!(flood.stream_refusal_percent(), 114);
3988 }
3989
3990 #[test]
3991 fn h2_stream_idle_timeout_inherits_back_timeout() {
3992 use std::time::Duration;
3993
3994 let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
3995 let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
3996 let mut cfg = ListenerBuilder::new_https(address)
3997 .to_tls(None)
3998 .expect("default HTTPS listener config");
3999 cfg.back_timeout = back_timeout;
4000 cfg.h2_stream_idle_timeout_seconds = explicit;
4001 HttpsListener::try_new(cfg, Token(0)).expect("build listener")
4002 };
4003
4004 assert_eq!(
4006 build(180, None).get_h2_stream_idle_timeout(),
4007 Duration::from_secs(180)
4008 );
4009
4010 assert_eq!(
4012 build(5, None).get_h2_stream_idle_timeout(),
4013 Duration::from_secs(30)
4014 );
4015
4016 assert_eq!(
4018 build(180, Some(10)).get_h2_stream_idle_timeout(),
4019 Duration::from_secs(10)
4020 );
4021 assert_eq!(
4022 build(5, Some(600)).get_h2_stream_idle_timeout(),
4023 Duration::from_secs(600)
4024 );
4025
4026 assert_eq!(
4028 build(180, Some(0)).get_h2_stream_idle_timeout(),
4029 Duration::from_secs(1)
4030 );
4031 }
4032
4033 const PROXY_ADVERTISED_PEER: &str = "10.0.0.42:12345";
4038
4039 #[test]
4070 fn front_rustls_peer_snapshot_is_the_session_peer_not_the_accepted_socket() {
4071 let advertised: StdSocketAddr = PROXY_ADVERTISED_PEER
4072 .parse()
4073 .expect("the advertised peer literal must parse");
4074
4075 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4076 let listener = proxy
4077 .listeners
4078 .values()
4079 .next()
4080 .expect("the test proxy has one listener")
4081 .clone();
4082 let pool = Rc::downgrade(&proxy.pool);
4083 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4084 .expect("a test rustls server session must be created");
4085 let public_address = *listener.borrow().get_addr();
4086 let proxy = Rc::new(RefCell::new(proxy));
4087
4088 let balancer = std::net::TcpListener::bind("127.0.0.1:0")
4093 .expect("test listener must bind to a loopback port");
4094 let balancer_peer = balancer
4095 .local_addr()
4096 .expect("test listener must report its local address");
4097 let stream =
4098 std::net::TcpStream::connect(balancer_peer).expect("loopback connect must complete");
4099 stream
4100 .set_nonblocking(true)
4101 .expect("mio requires a nonblocking stream");
4102 let stream = MioTcpStream::from_std(stream);
4103
4104 let mut session = HttpsSession::new(
4105 Duration::from_secs(30),
4106 Duration::from_secs(30),
4107 Duration::from_secs(30),
4108 Duration::from_secs(30),
4109 false,
4112 listener,
4113 pool,
4114 proxy,
4115 public_address,
4116 rustls_details,
4117 stream,
4118 balancer_peer,
4121 Token(0),
4122 Duration::from_secs(0),
4123 );
4124
4125 assert_eq!(
4126 session.peer_address,
4127 Some(balancer_peer),
4128 "a direct-route session starts out with the accepted socket's peer"
4129 );
4130
4131 let mut handshake = match session.state.take() {
4132 HttpsStateMachine::Handshake(handshake) => handshake,
4133 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4134 };
4135
4136 session.peer_address = Some(advertised);
4138 handshake.peer_address = Some(advertised);
4139 assert_ne!(
4140 advertised, balancer_peer,
4141 "the advertised and connection peers must differ for this test to discriminate"
4142 );
4143
4144 let upgraded = session
4145 .upgrade_handshake(handshake)
4146 .expect("the handshake must upgrade to a mux session");
4147 let (snapshot, live_lookup) = match &upgraded {
4150 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4151 mux::Connection::H1(connection) => (
4152 connection.socket.peer_addr(),
4153 connection.socket.socket_ref().peer_addr().ok(),
4154 ),
4155 mux::Connection::H2(connection) => (
4156 connection.socket.peer_addr(),
4157 connection.socket.socket_ref().peer_addr().ok(),
4158 ),
4159 },
4160 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4161 };
4162
4163 assert_eq!(
4166 live_lookup,
4167 Some(balancer_peer),
4168 "the frontend socket must still be connected to the stand-in balancer"
4169 );
4170 assert_eq!(
4171 snapshot,
4172 Some(advertised),
4173 "FrontRustls must carry the PROXY-advertised source, not the accepted socket's peer"
4174 );
4175 }
4176
4177 #[test]
4195 fn front_rustls_peer_addr_falls_back_to_the_live_lookup() {
4196 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4197 let listener = proxy
4198 .listeners
4199 .values()
4200 .next()
4201 .expect("the test proxy has one listener")
4202 .clone();
4203 let pool = Rc::downgrade(&proxy.pool);
4204 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4205 .expect("a test rustls server session must be created");
4206 let public_address = *listener.borrow().get_addr();
4207 let proxy = Rc::new(RefCell::new(proxy));
4208
4209 let peer = std::net::TcpListener::bind("127.0.0.1:0")
4210 .expect("test listener must bind to a loopback port");
4211 let live_peer = peer
4212 .local_addr()
4213 .expect("test listener must report its local address");
4214 let stream =
4215 std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
4216 stream
4217 .set_nonblocking(true)
4218 .expect("mio requires a nonblocking stream");
4219 let stream = MioTcpStream::from_std(stream);
4220
4221 let mut session = HttpsSession::new(
4222 Duration::from_secs(30),
4223 Duration::from_secs(30),
4224 Duration::from_secs(30),
4225 Duration::from_secs(30),
4226 false,
4227 listener,
4228 pool,
4229 proxy,
4230 public_address,
4231 rustls_details,
4232 stream,
4233 live_peer,
4234 Token(0),
4235 Duration::from_secs(0),
4236 );
4237
4238 let mut handshake = match session.state.take() {
4239 HttpsStateMachine::Handshake(handshake) => handshake,
4240 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4241 };
4242
4243 session.peer_address = None;
4245 handshake.peer_address = None;
4246
4247 let upgraded = session
4248 .upgrade_handshake(handshake)
4249 .expect("the handshake must upgrade to a mux session");
4250 let snapshot = match &upgraded {
4251 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4252 mux::Connection::H1(connection) => connection.socket.peer_addr(),
4253 mux::Connection::H2(connection) => connection.socket.peer_addr(),
4254 },
4255 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4256 };
4257
4258 assert_eq!(
4259 snapshot,
4260 Some(live_peer),
4261 "with nothing cached, FrontRustls::peer_addr must degrade to getpeername(2)"
4262 );
4263 }
4264
4265 #[test]
4282 fn query_certificate_for_domain_answers_any_spelling_of_the_name() {
4283 let mut proxy = proxy_with_certificate_domain("MiXeD.Example.COM".to_owned());
4284
4285 let mut summaries_for = |query: &str| {
4286 let response = proxy
4287 .query_certificate_for_domain(query.to_owned())
4288 .expect("the domain certificate query must succeed")
4289 .expect("the domain certificate query must return content");
4290 match response.content_type {
4291 Some(ContentType::CertificatesByAddress(list)) => list
4292 .certificates
4293 .iter()
4294 .map(|entry| entry.certificate_summaries.len())
4295 .sum::<usize>(),
4296 _ => panic!("the domain query must answer with CertificatesByAddress"),
4297 }
4298 };
4299
4300 for query in [
4301 "MiXeD.Example.COM",
4302 "mixed.example.com",
4303 "MIXED.EXAMPLE.COM",
4304 ] {
4305 assert_eq!(
4306 summaries_for(query),
4307 1,
4308 "querying {query:?} must find the certificate stored as MiXeD.Example.COM",
4309 );
4310 }
4311
4312 assert_eq!(
4313 summaries_for("other.example.com"),
4314 0,
4315 "normalising the query must not make an unrelated domain match",
4316 );
4317 }
4318
4319 fn https_mux_session_with_peer() -> (HttpsSession, std::net::TcpStream) {
4323 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4324 let listener = proxy
4325 .listeners
4326 .values()
4327 .next()
4328 .expect("the test proxy has one listener")
4329 .clone();
4330 let pool = Rc::downgrade(&proxy.pool);
4331 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4332 .expect("a test rustls server session must be created");
4333 let public_address = *listener.borrow().get_addr();
4334 let proxy = Rc::new(RefCell::new(proxy));
4335
4336 let accepting = std::net::TcpListener::bind("127.0.0.1:0")
4337 .expect("test listener must bind to a loopback port");
4338 let address = accepting
4339 .local_addr()
4340 .expect("test listener must report its local address");
4341 let stream = std::net::TcpStream::connect(address).expect("loopback connect must complete");
4342 let (client, client_address) = accepting.accept().expect("loopback accept must complete");
4343 client
4344 .set_nonblocking(true)
4345 .expect("the client stand-in must be nonblocking");
4346 stream
4347 .set_nonblocking(true)
4348 .expect("mio requires a nonblocking stream");
4349
4350 let mut session = HttpsSession::new(
4351 Duration::from_secs(30),
4352 Duration::from_secs(30),
4353 Duration::from_secs(30),
4354 Duration::from_secs(30),
4355 false,
4356 listener,
4357 pool,
4358 proxy,
4359 public_address,
4360 rustls_details,
4361 MioTcpStream::from_std(stream),
4362 client_address,
4363 Token(0),
4364 Duration::from_secs(0),
4365 );
4366 let handshake = match session.state.take() {
4367 HttpsStateMachine::Handshake(handshake) => handshake,
4368 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4369 };
4370 session.state = session
4371 .upgrade_handshake(handshake)
4372 .expect("the handshake must upgrade to a mux session");
4373 (session, client)
4374 }
4375
4376 fn frontend_shutdowns_of_a_close(
4380 mut session: HttpsSession,
4381 client: &mut std::net::TcpStream,
4382 ) -> (usize, bool) {
4383 use std::io::Read;
4384
4385 let before = mux::shutdown_writes();
4386 session.close();
4387 let shutdowns = mux::shutdown_writes() - before;
4388 drop(session);
4389
4390 let deadline = Instant::now() + Duration::from_secs(2);
4391 let mut chunk = [0u8; 4096];
4392 let saw_fin = loop {
4393 match client.read(&mut chunk) {
4394 Ok(0) => break true,
4395 Ok(_) => {}
4396 Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
4397 std::thread::sleep(Duration::from_millis(5));
4398 }
4399 Err(_) => break false,
4400 }
4401 };
4402 (shutdowns, saw_fin)
4403 }
4404
4405 #[test]
4421 fn https_close_skips_the_frontend_shutdown_once_the_client_closed() {
4422 for how in ["epoll HUP", "read EOF"] {
4423 let (mut session, mut client) = https_mux_session_with_peer();
4424 client
4425 .shutdown(std::net::Shutdown::Write)
4426 .expect("the client stand-in must send its FIN");
4427 let HttpsStateMachine::Mux(mux) = &mut session.state else {
4428 unreachable!("https_mux_session_with_peer returns a mux session")
4429 };
4430 if how == "epoll HUP" {
4431 mux.frontend.readiness_mut().event.insert(Ready::HUP);
4432 } else {
4433 match &mut mux.frontend {
4434 mux::Connection::H1(connection) => connection.socket.peer_disconnected = true,
4435 mux::Connection::H2(connection) => connection.socket.peer_disconnected = true,
4436 }
4437 }
4438
4439 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4440 assert_eq!(
4441 shutdowns, 0,
4442 "a frontend whose client closed ({how}) must not be shut down before its close"
4443 );
4444 assert!(
4445 saw_fin,
4446 "the close alone must still deliver the FIN to a half-closed client ({how})"
4447 );
4448 }
4449 }
4450
4451 #[test]
4454 fn https_close_keeps_the_frontend_shutdown_while_the_client_is_there() {
4455 let (session, mut client) = https_mux_session_with_peer();
4456 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4457 assert_eq!(
4458 shutdowns, 1,
4459 "a frontend whose client is still there must keep its shutdown(SHUT_WR)"
4460 );
4461 assert!(saw_fin, "the client must observe the FIN");
4462 }
4463}
4464
4465#[cfg(test)]
4466mod listener_sibling_tests {
4467 use sozu_command::{
4468 config::ListenerBuilder,
4469 proto::command::{
4470 CertificateAndKey, HstsConfig, PathRule, RequestHttpFrontend, SocketAddress,
4471 },
4472 };
4473
4474 use super::*;
4475 use crate::testing::{ServerParts, prebuild_server, provide_port};
4476
4477 fn proxy() -> HttpsProxy {
4478 let ServerParts {
4479 registry,
4480 sessions,
4481 pool,
4482 backends,
4483 ..
4484 } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4485 HttpsProxy::new(registry, sessions, pool, backends)
4486 }
4487
4488 fn add(proxy: &mut HttpsProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
4489 let config = ListenerBuilder::new_https(address)
4490 .with_interface(interface)
4491 .to_tls(None)
4492 .expect("could not build the listener config");
4493 proxy
4494 .add_listener(config, Token(token))
4495 .expect("could not add the listener");
4496 }
4497
4498 fn certificate(address: SocketAddress) -> AddCertificate {
4499 AddCertificate {
4500 address,
4501 certificate: CertificateAndKey {
4502 certificate: include_str!("../assets/certificate.pem").to_owned(),
4503 key: include_str!("../assets/key.pem").to_owned(),
4504 certificate_chain: Vec::new(),
4505 versions: Vec::new(),
4506 names: vec!["example.com".to_owned()],
4507 },
4508 expired_at: None,
4509 }
4510 }
4511
4512 fn front(address: SocketAddress) -> RequestHttpFrontend {
4513 RequestHttpFrontend {
4514 cluster_id: Some("cluster-a".to_owned()),
4515 address,
4516 hostname: "example.com".to_owned(),
4517 path: PathRule::prefix("/".to_owned()),
4518 ..Default::default()
4519 }
4520 }
4521
4522 fn serves_certificate(proxy: &HttpsProxy, token: usize) -> bool {
4523 proxy.listeners[&Token(token)]
4524 .borrow()
4525 .resolver
4526 .0
4527 .lock()
4528 .expect("resolver lock")
4529 .domains
4530 .domain_lookup(b"example.com", true)
4531 .is_some()
4532 }
4533
4534 fn routes(proxy: &HttpsProxy, token: usize) -> bool {
4535 proxy.listeners[&Token(token)]
4536 .borrow()
4537 .fronts
4538 .lookup("example.com", "/", &Method::Get)
4539 .is_ok()
4540 }
4541
4542 #[test]
4548 fn a_listener_added_next_to_a_sibling_takes_over_its_certificates_and_frontends() {
4549 let mut proxy = proxy();
4550 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4551 add(&mut proxy, address, None, 0);
4552 proxy
4553 .add_certificate(certificate(address))
4554 .expect("add the certificate");
4555 proxy
4556 .add_https_frontend(front(address))
4557 .expect("add the frontend");
4558 add(&mut proxy, address, Some("lo"), 1);
4559
4560 assert!(serves_certificate(&proxy, 0));
4561 assert!(
4562 serves_certificate(&proxy, 1),
4563 "the listener added after the certificate must serve it too"
4564 );
4565 assert!(
4566 routes(&proxy, 1),
4567 "the listener added after the frontend must route it too"
4568 );
4569
4570 proxy
4571 .remove_https_frontend(front(address))
4572 .expect("remove the frontend");
4573 assert!(!routes(&proxy, 0));
4574 assert!(!routes(&proxy, 1));
4575 }
4576 fn hsts(max_age: u32) -> HstsConfig {
4577 HstsConfig {
4578 enabled: Some(true),
4579 max_age: Some(max_age),
4580 ..Default::default()
4581 }
4582 }
4583
4584 fn add_with_hsts(
4585 proxy: &mut HttpsProxy,
4586 address: SocketAddress,
4587 interface: Option<&str>,
4588 token: usize,
4589 hsts: Option<HstsConfig>,
4590 ) {
4591 let mut config = ListenerBuilder::new_https(address)
4592 .with_interface(interface)
4593 .to_tls(None)
4594 .expect("could not build the listener config");
4595 config.hsts = hsts;
4596 proxy
4597 .add_listener(config, Token(token))
4598 .expect("could not add the listener");
4599 }
4600
4601 fn tagged_front(address: SocketAddress) -> RequestHttpFrontend {
4604 RequestHttpFrontend {
4605 tags: BTreeMap::from([("owner".to_owned(), "team-a".to_owned())]),
4606 ..front(address)
4607 }
4608 }
4609
4610 fn served_hsts(proxy: &HttpsProxy, token: usize) -> Option<String> {
4613 let route = proxy.listeners[&Token(token)]
4614 .borrow()
4615 .fronts
4616 .lookup("example.com", "/", &Method::Get)
4617 .expect("the listener must route example.com");
4618 route
4619 .headers_response
4620 .iter()
4621 .find(|edit| edit.key.eq_ignore_ascii_case(b"strict-transport-security"))
4622 .map(|edit| String::from_utf8_lossy(&edit.val).into_owned())
4623 }
4624
4625 fn patch_hsts(proxy: &HttpsProxy, token: usize, address: SocketAddress, hsts: HstsConfig) {
4626 proxy.listeners[&Token(token)]
4627 .borrow_mut()
4628 .update_config(&UpdateHttpsListenerConfig {
4629 address,
4630 hsts: Some(hsts),
4631 ..Default::default()
4632 })
4633 .expect("patch the listener HSTS default");
4634 }
4635
4636 #[test]
4649 fn a_rejected_patch_leaves_the_listener_unchanged() {
4650 use sozu_command::proto::command::{AlpnProtocols, UpdateHttpsListenerConfig};
4651
4652 let mut proxy = proxy();
4653 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4654 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4655 proxy
4656 .add_https_frontend(tagged_front(address))
4657 .expect("add the frontend");
4658
4659 let valid_prefix = UpdateHttpsListenerConfig {
4660 address,
4661 front_timeout: Some(99),
4662 sozu_id_header: Some("X-Edge-Id".to_owned()),
4663 alpn_protocols: Some(AlpnProtocols {
4664 values: vec!["http/1.1".to_owned()],
4665 }),
4666 answers: BTreeMap::from([(
4667 "404".to_owned(),
4668 "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_owned(),
4669 )]),
4670 ..Default::default()
4671 };
4672 let rejected = [
4673 UpdateHttpsListenerConfig {
4674 hsts: Some(HstsConfig {
4675 enabled: None,
4676 max_age: Some(200),
4677 ..Default::default()
4678 }),
4679 ..valid_prefix.clone()
4680 },
4681 UpdateHttpsListenerConfig {
4682 answers: BTreeMap::from([(
4683 "503".to_owned(),
4684 "not a valid http response".to_owned(),
4685 )]),
4686 hsts: Some(hsts(200)),
4687 ..valid_prefix.clone()
4688 },
4689 ];
4690 for patch in rejected {
4691 let listener = &proxy.listeners[&Token(0)];
4692 let config_before = listener.borrow().config.clone();
4693 let rustls_before = listener.borrow().rustls_details.clone();
4694 let answers_before = listener.borrow().get_answers().clone();
4695 listener
4696 .borrow_mut()
4697 .update_config(&patch)
4698 .expect_err("the patch must be refused");
4699 let listener = listener.borrow();
4700 assert_eq!(
4701 listener.config, config_before,
4702 "a refused patch must leave the listener configuration unchanged"
4703 );
4704 assert!(
4705 Arc::ptr_eq(&rustls_before, &listener.rustls_details),
4706 "a refused patch must not publish a new rustls context"
4707 );
4708 assert!(
4709 Rc::ptr_eq(&answers_before, listener.get_answers()),
4710 "a refused patch must not publish a new answer registry"
4711 );
4712 drop(listener);
4713 assert_eq!(
4714 served_hsts(&proxy, 0).as_deref(),
4715 Some("max-age=100"),
4716 "a refused patch must not refresh the frontends' HSTS"
4717 );
4718 }
4719
4720 proxy.listeners[&Token(0)]
4721 .borrow_mut()
4722 .update_config(&valid_prefix)
4723 .expect("the valid prefix alone must apply");
4724 let listener = proxy.listeners[&Token(0)].borrow();
4725 assert_eq!(listener.config.front_timeout, 99);
4726 assert_eq!(listener.config.alpn_protocols, vec!["http/1.1".to_owned()]);
4727 }
4728
4729 #[test]
4734 fn a_copied_frontend_without_hsts_inherits_the_new_listener_default() {
4735 let mut proxy = proxy();
4736 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4737 add_with_hsts(&mut proxy, address, None, 0, None);
4738 proxy
4739 .add_https_frontend(tagged_front(address))
4740 .expect("add the frontend");
4741 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(100)));
4742
4743 assert_eq!(served_hsts(&proxy, 0), None);
4744 assert_eq!(
4745 served_hsts(&proxy, 1).as_deref(),
4746 Some("max-age=100"),
4747 "the listener added after the frontend must serve its own HSTS default"
4748 );
4749
4750 patch_hsts(&proxy, 0, address, hsts(200));
4752 patch_hsts(&proxy, 1, address, hsts(300));
4753 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=200"));
4754 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=300"));
4755 }
4756
4757 #[test]
4760 fn a_frontend_with_explicit_hsts_is_never_overridden_by_a_listener_default() {
4761 let mut proxy = proxy();
4762 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4763 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4764 proxy
4765 .add_https_frontend(RequestHttpFrontend {
4766 hsts: Some(hsts(42)),
4767 ..tagged_front(address)
4768 })
4769 .expect("add the frontend");
4770 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(300)));
4771
4772 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4773 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4774
4775 patch_hsts(&proxy, 0, address, hsts(500));
4776 patch_hsts(&proxy, 1, address, hsts(600));
4777 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4778 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4779 }
4780
4781 #[test]
4785 fn a_listener_hsts_patch_refreshes_every_frontend_without_its_own_hsts() {
4786 for initial in [None, Some(hsts(100))] {
4787 let mut proxy = proxy();
4788 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4789 add_with_hsts(&mut proxy, address, None, 0, initial);
4790 proxy
4791 .add_https_frontend(tagged_front(address))
4792 .expect("add the frontend");
4793
4794 patch_hsts(&proxy, 0, address, hsts(700));
4795 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=700"));
4796
4797 patch_hsts(
4798 &proxy,
4799 0,
4800 address,
4801 HstsConfig {
4802 enabled: Some(false),
4803 ..Default::default()
4804 },
4805 );
4806 assert_eq!(served_hsts(&proxy, 0), None);
4807 }
4808 }
4809
4810 const TEST_CA_PEM: &str = include_str!("../assets/certificate.pem");
4812
4813 fn test_crypto_provider() -> Arc<CryptoProvider> {
4814 Arc::new(default_provider())
4815 }
4816
4817 fn https_config_with_client_auth(
4818 mode: ClientAuthMode,
4819 cas: &[&str],
4820 crls: &[&str],
4821 ) -> HttpsListenerConfig {
4822 let address = SocketAddress::new_v4(127, 0, 0, 1, 1049);
4823 let mut config = ListenerBuilder::new_https(address)
4824 .to_tls(None)
4825 .expect("default HTTPS listener config");
4826 config.client_auth = Some(mode as i32);
4827 config.client_ca_certificates = cas.iter().map(|s| s.to_string()).collect();
4828 config.client_ca_crls = crls.iter().map(|s| s.to_string()).collect();
4829 config
4830 }
4831
4832 #[test]
4833 fn client_cert_verifier_none_is_disabled() {
4834 let provider = test_crypto_provider();
4836 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthNone, &[], &[]);
4837 assert!(
4838 HttpsListener::client_cert_verifier(&config, &provider)
4839 .expect("verifier build")
4840 .is_none()
4841 );
4842
4843 let mut absent = config;
4844 absent.client_auth = None;
4845 assert!(
4846 HttpsListener::client_cert_verifier(&absent, &provider)
4847 .expect("verifier build")
4848 .is_none(),
4849 "absent client_auth must decode to NONE"
4850 );
4851 }
4852
4853 #[test]
4854 fn client_cert_verifier_rejects_unknown_mode() {
4855 let provider = test_crypto_provider();
4858 let mut config =
4859 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4860 config.client_auth = Some(999);
4861 assert!(matches!(
4862 HttpsListener::client_cert_verifier(&config, &provider),
4863 Err(ListenerError::ClientAuth(_))
4864 ));
4865 }
4866
4867 #[test]
4868 fn client_cert_verifier_required_builds_with_ca() {
4869 let provider = test_crypto_provider();
4870 let config =
4871 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4872 assert!(
4873 HttpsListener::client_cert_verifier(&config, &provider)
4874 .expect("verifier build")
4875 .is_some()
4876 );
4877 }
4878
4879 #[test]
4880 fn client_cert_verifier_optional_builds_with_ca() {
4881 let provider = test_crypto_provider();
4882 let config =
4883 https_config_with_client_auth(ClientAuthMode::ClientAuthOptional, &[TEST_CA_PEM], &[]);
4884 assert!(
4885 HttpsListener::client_cert_verifier(&config, &provider)
4886 .expect("verifier build")
4887 .is_some()
4888 );
4889 }
4890
4891 #[test]
4892 fn client_cert_verifier_requires_a_trusted_ca() {
4893 let provider = test_crypto_provider();
4896 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[], &[]);
4897 assert!(matches!(
4898 HttpsListener::client_cert_verifier(&config, &provider),
4899 Err(ListenerError::ClientAuth(_))
4900 ));
4901 }
4902
4903 #[test]
4904 fn client_cert_verifier_rejects_malformed_ca() {
4905 let provider = test_crypto_provider();
4906 let config = https_config_with_client_auth(
4907 ClientAuthMode::ClientAuthRequired,
4908 &["not a pem certificate"],
4909 &[],
4910 );
4911 assert!(matches!(
4913 HttpsListener::client_cert_verifier(&config, &provider),
4914 Err(ListenerError::ClientAuth(_))
4915 ));
4916 }
4917
4918 #[test]
4919 fn client_cert_verifier_rejects_empty_ca_entry_among_valid_ones() {
4920 let provider = test_crypto_provider();
4924 let config = https_config_with_client_auth(
4925 ClientAuthMode::ClientAuthRequired,
4926 &[TEST_CA_PEM, "not a certificate"],
4927 &[],
4928 );
4929 assert!(matches!(
4930 HttpsListener::client_cert_verifier(&config, &provider),
4931 Err(ListenerError::ClientAuth(_))
4932 ));
4933 }
4934
4935 #[test]
4936 fn client_cert_verifier_rejects_empty_crl_entry() {
4937 let provider = test_crypto_provider();
4940 let config = https_config_with_client_auth(
4941 ClientAuthMode::ClientAuthRequired,
4942 &[TEST_CA_PEM],
4943 &["not a crl"],
4944 );
4945 assert!(matches!(
4946 HttpsListener::client_cert_verifier(&config, &provider),
4947 Err(ListenerError::ClientAuth(_))
4948 ));
4949 }
4950
4951 const MTLS_CA_PEM: &str = include_str!("../assets/mtls/ca-cert.pem");
4960 const MTLS_CLIENT_PEM: &str = include_str!("../assets/mtls/client-cert.pem");
4961 const MTLS_CRL_CURRENT: &str = include_str!("../assets/mtls/crl-current.pem");
4963 const MTLS_CRL_REVOKED: &str = include_str!("../assets/mtls/crl-revoked.pem");
4965 const MTLS_CRL_EXPIRED: &str = include_str!("../assets/mtls/crl-expired.pem");
4967 const MTLS_CRL_OTHER_CA: &str = include_str!("../assets/mtls/crl-other-ca.pem");
4969 const MTLS_CRL_PARTITION_A: &str = include_str!("../assets/mtls/crl-partition-a.pem");
4972 const MTLS_CRL_PARTITION_B: &str = include_str!("../assets/mtls/crl-partition-b.pem");
4973
4974 fn verify_mtls_client(crls: &[&str]) -> Result<(), rustls::Error> {
4977 verify_mtls_client_at(crls, rustls::pki_types::UnixTime::now())
4978 }
4979
4980 fn verify_mtls_client_at(
4982 crls: &[&str],
4983 time: rustls::pki_types::UnixTime,
4984 ) -> Result<(), rustls::Error> {
4985 let config =
4986 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4987 let verifier = HttpsListener::client_cert_verifier(&config, &test_crypto_provider())
4988 .expect("a CA and well-formed CRLs must build a verifier")
4989 .expect("required client auth must install a verifier");
4990 let client = CertificateDer::from_pem_slice(MTLS_CLIENT_PEM.as_bytes())
4991 .expect("the client fixture is a PEM certificate");
4992 verifier.verify_client_cert(&client, &[], time).map(|_| ())
4993 }
4994
4995 fn build_mtls_listener(crls: &[&str]) -> Result<(), String> {
4998 let config =
4999 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
5000 match HttpsListener::client_cert_verifier(&config, &test_crypto_provider()) {
5001 Ok(_) => Ok(()),
5002 Err(ListenerError::ClientAuth(reason)) => Err(reason),
5003 Err(other) => panic!("unexpected listener error {other:?}"),
5004 }
5005 }
5006
5007 #[test]
5008 fn client_revocation_accepts_a_client_no_crl_revokes() {
5009 assert_eq!(
5010 verify_mtls_client(&[]),
5011 Ok(()),
5012 "precondition: the client chains to the CA"
5013 );
5014 assert_eq!(
5015 verify_mtls_client(&[MTLS_CRL_CURRENT]),
5016 Ok(()),
5017 "a current CRL from the client's issuer that does not list it must not reject it"
5018 );
5019 }
5020
5021 #[test]
5022 fn client_revocation_rejects_a_revoked_client() {
5023 assert_eq!(
5026 verify_mtls_client(&[MTLS_CRL_REVOKED]),
5027 Err(rustls::Error::InvalidCertificate(
5028 rustls::CertificateError::Revoked
5029 ))
5030 );
5031 }
5032
5033 #[test]
5034 fn client_revocation_refuses_an_expired_crl_only_where_the_operator_states_it() {
5035 let now = i64::try_from(
5038 std::time::SystemTime::now()
5039 .duration_since(std::time::UNIX_EPOCH)
5040 .expect("a clock after 1970")
5041 .as_secs(),
5042 )
5043 .expect("a clock within i64");
5044 let refused = HttpsListener::check_crls_current(&[MTLS_CRL_EXPIRED.to_owned()], now);
5045 assert!(
5046 matches!(refused, Err(ListenerError::ClientAuth(ref reason)) if reason.contains("expired")),
5047 "an expired CRL must be refused when stated, got {refused:?}"
5048 );
5049 assert!(
5050 HttpsListener::check_crls_current(
5051 &[MTLS_CRL_CURRENT.to_owned(), MTLS_CRL_OTHER_CA.to_owned()],
5052 now
5053 )
5054 .is_ok()
5055 );
5056 assert_eq!(build_mtls_listener(&[MTLS_CRL_EXPIRED]), Ok(()));
5060 let config = https_config_with_client_auth(
5061 ClientAuthMode::ClientAuthRequired,
5062 &[MTLS_CA_PEM],
5063 &[MTLS_CRL_EXPIRED],
5064 );
5065 assert!(HttpsListener::try_new(config, Token(0)).is_ok());
5066 }
5067
5068 #[test]
5069 fn client_revocation_rejects_a_client_once_its_crl_has_lapsed() {
5070 let june_2125 =
5077 rustls::pki_types::UnixTime::since_unix_epoch(Duration::from_secs(4_905_100_800));
5078 let verdict = verify_mtls_client_at(&[MTLS_CRL_CURRENT], june_2125);
5079 assert!(
5080 matches!(
5081 verdict,
5082 Err(rustls::Error::InvalidCertificate(
5083 rustls::CertificateError::ExpiredRevocationList
5084 | rustls::CertificateError::ExpiredRevocationListContext { .. }
5085 ))
5086 ),
5087 "a CRL past its nextUpdate must reject the client, got {verdict:?}"
5088 );
5089 assert_eq!(verify_mtls_client_at(&[], june_2125), Ok(()));
5092 }
5093
5094 #[test]
5095 fn client_revocation_rejects_a_client_no_configured_crl_covers() {
5096 assert_eq!(
5100 verify_mtls_client(&[MTLS_CRL_OTHER_CA]),
5101 Err(rustls::Error::InvalidCertificate(
5102 rustls::CertificateError::UnknownRevocationStatus
5103 ))
5104 );
5105 assert_eq!(
5107 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_CURRENT]),
5108 Ok(())
5109 );
5110 }
5111
5112 #[test]
5113 fn client_revocation_refuses_two_crls_of_one_issuer_with_overlapping_scopes() {
5114 for crls in [
5118 vec![MTLS_CRL_CURRENT, MTLS_CRL_REVOKED],
5119 vec![MTLS_CRL_REVOKED, MTLS_CRL_CURRENT],
5120 vec![MTLS_CRL_CURRENT, MTLS_CRL_PARTITION_A],
5122 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_CURRENT],
5123 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_A],
5125 ] {
5126 let refused = build_mtls_listener(&crls);
5127 assert!(
5128 refused
5129 .as_ref()
5130 .is_err_and(|reason| reason.contains("overlapping")),
5131 "overlapping CRLs of one issuer must fail the listener, got {refused:?}"
5132 );
5133 }
5134 assert!(build_mtls_listener(&[&format!("{MTLS_CRL_CURRENT}{MTLS_CRL_REVOKED}")]).is_err());
5136 }
5137
5138 #[test]
5139 fn client_revocation_takes_the_partitions_of_one_issuer() {
5140 assert_eq!(
5143 build_mtls_listener(&[MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_B]),
5144 Ok(())
5145 );
5146 }
5147
5148 #[test]
5149 fn client_revocation_takes_one_crl_per_issuer() {
5150 assert_eq!(
5153 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_REVOKED]),
5154 Err(rustls::Error::InvalidCertificate(
5155 rustls::CertificateError::Revoked
5156 ))
5157 );
5158 assert_eq!(
5159 verify_mtls_client(&[MTLS_CRL_REVOKED, MTLS_CRL_OTHER_CA]),
5160 Err(rustls::Error::InvalidCertificate(
5161 rustls::CertificateError::Revoked
5162 ))
5163 );
5164 }
5165
5166 #[test]
5172 fn a_client_auth_policy_patch_rebuilds_the_listener_verifier() {
5173 let config = https_config_with_client_auth(
5174 ClientAuthMode::ClientAuthRequired,
5175 &[MTLS_CA_PEM],
5176 &[MTLS_CRL_CURRENT],
5177 );
5178 let mut listener =
5179 HttpsListener::try_new(config.clone(), Token(0)).expect("build the mTLS listener");
5180 let original_context = listener.rustls_details.clone();
5181
5182 let refreshed = HttpsListenerConfig {
5183 client_ca_crls: vec![MTLS_CRL_REVOKED.to_owned()],
5184 ..config.clone()
5185 }
5186 .client_auth_policy();
5187 listener
5188 .update_config(&UpdateHttpsListenerConfig {
5189 address: config.address,
5190 client_auth_policy: Some(refreshed.clone()),
5191 ..Default::default()
5192 })
5193 .expect("a buildable policy must be applied");
5194 assert_eq!(listener.config.client_auth_policy(), refreshed);
5195 assert!(
5196 !Arc::ptr_eq(&listener.rustls_details, &original_context),
5197 "the verifier lives in the rustls context, which must be rebuilt"
5198 );
5199
5200 let applied_context = listener.rustls_details.clone();
5201 let unbuildable = ClientAuthPolicy {
5202 client_auth: Some(ClientAuthMode::ClientAuthRequired as i32),
5203 client_ca_certificates: vec![],
5204 client_ca_crls: vec![],
5205 };
5206 assert!(
5207 listener
5208 .update_config(&UpdateHttpsListenerConfig {
5209 address: config.address,
5210 client_auth_policy: Some(unbuildable.clone()),
5211 ..Default::default()
5212 })
5213 .is_err(),
5214 "required client auth with no trusted CA must be refused"
5215 );
5216 assert!(HttpsListener::validate_client_auth_policy(&unbuildable).is_err());
5217 assert_eq!(
5218 listener.config.client_auth_policy(),
5219 refreshed,
5220 "a refused policy leaves the applied one in place"
5221 );
5222 assert!(Arc::ptr_eq(&listener.rustls_details, &applied_context));
5223 }
5224}