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::{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 last_event(&self) -> Instant {
1239 self.last_event
1240 }
1241
1242 fn print_session(&self) {
1243 self.state.print_state("HTTPS");
1244 error!("{} Metrics: {:?}", log_context!(self), self.metrics);
1245 }
1246
1247 fn frontend_token(&self) -> Token {
1248 self.frontend_token
1249 }
1250}
1251
1252pub type HostName = String;
1253pub type PathBegin = String;
1254
1255pub struct HttpsListener {
1256 active: bool,
1257 address: StdSocketAddr,
1258 answers: Rc<RefCell<HttpAnswers>>,
1259 config: HttpsListenerConfig,
1260 fronts: Router,
1261 listener: Option<MioTcpListener>,
1262 parked_listener: Option<MioTcpListener>,
1276 nodelay_backlog: bool,
1282 resolver: Arc<MutexCertificateResolver>,
1283 rustls_details: Arc<RustlsServerConfig>,
1284 tags: BTreeMap<String, CachedTags>,
1285 token: Token,
1286}
1287
1288impl ListenerHandler for HttpsListener {
1289 fn get_addr(&self) -> &StdSocketAddr {
1290 &self.address
1291 }
1292
1293 fn get_tags(&self, key: &str) -> Option<&CachedTags> {
1294 self.tags.get(key)
1295 }
1296
1297 fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
1298 match tags {
1299 Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
1300 None => self.tags.remove(&key),
1301 };
1302 }
1303
1304 fn protocol(&self) -> Protocol {
1305 Protocol::HTTPS
1306 }
1307
1308 fn public_address(&self) -> StdSocketAddr {
1309 self.config
1310 .public_address
1311 .map(|addr| addr.into())
1312 .unwrap_or(self.address)
1313 }
1314}
1315
1316impl L7ListenerHandler for HttpsListener {
1317 fn get_sticky_name(&self) -> &str {
1318 &self.config.sticky_name
1319 }
1320
1321 fn get_sozu_id_header(&self) -> &str {
1322 self.config
1323 .sozu_id_header
1324 .as_deref()
1325 .filter(|s| !s.is_empty())
1326 .unwrap_or("Sozu-Id")
1327 }
1328
1329 fn get_connect_timeout(&self) -> u32 {
1330 self.config.connect_timeout
1331 }
1332
1333 fn frontend_from_request(
1334 &self,
1335 host: &str,
1336 uri: &str,
1337 method: &Method,
1338 ) -> Result<RouteResult, FrontendFromRequestError> {
1339 let start = Instant::now();
1340 let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
1341 Ok(tuple) => tuple,
1342 Err(parse_error) => {
1343 return Err(FrontendFromRequestError::HostParse {
1345 host: host.to_owned(),
1346 error: parse_error.to_string(),
1347 });
1348 }
1349 };
1350
1351 if remaining_input != &b""[..] {
1352 return Err(FrontendFromRequestError::InvalidCharsAfterHost(
1353 host.to_owned(),
1354 ));
1355 }
1356
1357 let host = unsafe { from_utf8_unchecked(hostname) };
1366
1367 let route = self.fronts.lookup(host, uri, method).map_err(|e| {
1368 incr!(names::http::FAILED_BACKEND_MATCHING);
1369 FrontendFromRequestError::NoClusterFound(e)
1370 })?;
1371
1372 let now = Instant::now();
1373
1374 if let Some(cluster) = route.cluster_id.as_deref() {
1375 time!(
1376 names::event_loop::FRONTEND_MATCHING_TIME,
1377 cluster,
1378 (now - start).as_millis()
1379 );
1380 }
1381
1382 Ok(route)
1383 }
1384
1385 fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
1386 &self.answers
1387 }
1388
1389 fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
1390 crate::protocol::mux::H2FloodConfig::from_optional(
1398 self.config.h2_max_rst_stream_per_window,
1399 self.config.h2_max_ping_per_window,
1400 self.config.h2_max_settings_per_window,
1401 self.config.h2_max_empty_data_per_window,
1402 self.config.h2_max_window_update_stream0_per_window,
1403 self.config.h2_max_continuation_frames,
1404 self.config.h2_max_glitch_count,
1405 self.config.h2_max_rst_stream_lifetime,
1406 self.config.h2_max_rst_stream_abusive_lifetime,
1407 self.config.h2_max_rst_stream_emitted_lifetime,
1408 self.config.h2_max_header_list_size,
1409 self.config.h2_max_header_table_size,
1410 self.config.h2_max_header_fields,
1411 self.config.h2_stream_refusal_percent,
1412 )
1413 }
1414
1415 fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1416 crate::protocol::mux::H2ConnectionConfig::from_optional(
1417 self.config.h2_initial_connection_window,
1418 self.config.h2_max_concurrent_streams,
1419 self.config.h2_stream_shrink_ratio,
1420 )
1421 }
1422
1423 fn get_strict_sni_binding(&self) -> bool {
1424 self.config.strict_sni_binding.unwrap_or(true)
1439 }
1440
1441 fn get_elide_x_real_ip(&self) -> bool {
1442 self.config.elide_x_real_ip.unwrap_or(false)
1443 }
1444
1445 fn get_send_x_real_ip(&self) -> bool {
1446 self.config.send_x_real_ip.unwrap_or(false)
1447 }
1448
1449 fn get_forwarded_headers(&self) -> ForwardedHeaders {
1450 self.config.forwarded_headers()
1453 }
1454
1455 fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1456 let seconds = self
1463 .config
1464 .h2_stream_idle_timeout_seconds
1465 .map(|s| u64::from(s.max(1)))
1466 .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1467 std::time::Duration::from_secs(seconds)
1468 }
1469
1470 fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1471 match self.config.h2_graceful_shutdown_deadline_seconds {
1472 None => Some(std::time::Duration::from_secs(5)),
1473 Some(0) => None,
1474 Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1475 }
1476 }
1477}
1478
1479impl HttpsListener {
1480 pub fn key(&self) -> ListenerKey {
1483 ListenerKey::new(self.address, self.config.interface.as_deref())
1484 }
1485
1486 pub fn is(&self, key: &ListenerKey) -> bool {
1488 key.matches(&self.address, self.config.interface.as_deref())
1489 }
1490
1491 pub fn is_http11_disabled(&self) -> bool {
1497 self.config.disable_http11.unwrap_or(false)
1498 }
1499
1500 pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1506 &self.resolver
1507 }
1508
1509 pub fn try_new(
1510 config: HttpsListenerConfig,
1511 token: Token,
1512 ) -> Result<HttpsListener, ListenerError> {
1513 if let Some(v) = config.forwarded_headers {
1516 validate_forwarded_headers(v)?;
1517 }
1518 let resolver = Arc::new(MutexCertificateResolver::default());
1519
1520 let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1521
1522 let answers = Self::build_answers(&config)?;
1523
1524 Ok(HttpsListener {
1525 listener: None,
1526 parked_listener: None,
1527 nodelay_backlog: false,
1528 address: config.address.into(),
1529 resolver,
1530 rustls_details: server_config,
1531 active: false,
1532 fronts: Router::new(),
1533 answers: Rc::new(RefCell::new(answers)),
1534 config,
1535 token,
1536 tags: BTreeMap::new(),
1537 })
1538 }
1539
1540 fn build_answers(config: &HttpsListenerConfig) -> Result<HttpAnswers, ListenerError> {
1547 let mut answers_map = config.answers.clone();
1548 if let Some(ref legacy) = config.http_answers {
1549 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1550 }
1551 HttpAnswers::new(&answers_map)
1552 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1553 }
1554
1555 pub fn validate_config(config: &HttpsListenerConfig) -> Result<(), ListenerError> {
1566 Self::create_rustls_context(config, Arc::new(MutexCertificateResolver::default()))?;
1576 Self::build_answers(config)?;
1577 Ok(())
1578 }
1579
1580 pub fn activate(
1581 &mut self,
1582 registry: &Registry,
1583 tcp_listener: Option<MioTcpListener>,
1584 ) -> Result<Token, ListenerError> {
1585 if self.active {
1586 return Ok(self.token);
1587 }
1588 let address: StdSocketAddr = self.config.address.into();
1589
1590 let parked = self.parked_listener.take();
1596 let mut listener = match (tcp_listener, parked) {
1597 (Some(inherited), Some(parked)) => {
1598 warn!(
1606 "{} closing the socket parked on the HTTPS listener for {}: an \
1607 inherited socket arrived for the same address and is the one being \
1608 activated",
1609 log_module_context!(),
1610 address
1611 );
1612 drop(parked);
1613 inherited
1614 }
1615 (Some(inherited), None) => inherited,
1616 (None, Some(parked)) => parked,
1617 (None, None) => server_bind(address, self.config.interface.as_deref()).map_err(
1618 |server_bind_error| ListenerError::Activation {
1619 address,
1620 error: server_bind_error.to_string(),
1621 },
1622 )?,
1623 };
1624
1625 if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
1629 error!(
1630 "{} error setting nodelay on listen socket({:?}): {:?}",
1631 log_module_context!(),
1632 listener,
1633 e
1634 );
1635 }
1636
1637 let registration = registry
1638 .register(&mut listener, self.token, Interest::READABLE)
1639 .map_err(ListenerError::SocketRegistration);
1640 if let Err(error) = registration {
1641 self.parked_listener = Some(listener);
1651 return Err(error);
1652 }
1653
1654 self.listener = Some(listener);
1655 self.nodelay_backlog = true;
1656 self.active = true;
1657 debug_assert!(
1663 self.active && self.listener.is_some(),
1664 "an activated HTTPS listener must hold a bound socket and be flagged active"
1665 );
1666 Ok(self.token)
1667 }
1668
1669 pub fn create_rustls_context(
1670 config: &HttpsListenerConfig,
1671 resolver: Arc<MutexCertificateResolver>,
1672 ) -> Result<RustlsServerConfig, ListenerError> {
1673 let cipher_names = if config.cipher_list.is_empty() {
1674 DEFAULT_CIPHER_LIST.to_vec()
1675 } else {
1676 config
1677 .cipher_list
1678 .iter()
1679 .map(|s| s.as_str())
1680 .collect::<Vec<_>>()
1681 };
1682
1683 let ciphers = cipher_names
1684 .into_iter()
1685 .filter_map(|cipher| {
1686 cipher_suite_by_name(cipher).or_else(|| {
1687 error!(
1688 "{} unknown or unsupported cipher: {:?}",
1689 log_module_context!(),
1690 cipher
1691 );
1692 None
1693 })
1694 })
1695 .collect::<Vec<_>>();
1696
1697 let versions = config
1698 .versions
1699 .iter()
1700 .filter_map(|version| match TlsVersion::try_from(*version) {
1701 Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1702 Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1703 Ok(other_version) => {
1704 error!(
1705 "{} unsupported TLS version {:?}",
1706 log_module_context!(),
1707 other_version
1708 );
1709 None
1710 }
1711 Err(_) => {
1712 error!("{} unsupported TLS version", log_module_context!());
1713 None
1714 }
1715 })
1716 .collect::<Vec<_>>();
1717
1718 let kx_groups = if config.groups_list.is_empty() {
1719 default_provider().kx_groups
1720 } else {
1721 config
1722 .groups_list
1723 .iter()
1724 .filter_map(|group| match kx_group_by_name(group) {
1725 Some(kx) => Some(kx),
1726 None => {
1727 debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1728 None
1729 }
1730 })
1731 .collect::<Vec<_>>()
1732 };
1733
1734 let provider = Arc::new(CryptoProvider {
1735 cipher_suites: ciphers,
1736 kx_groups,
1737 ..default_provider()
1738 });
1739
1740 let builder = RustlsServerConfig::builder_with_provider(provider.clone())
1741 .with_protocol_versions(&versions[..])
1742 .map_err(|err| ListenerError::BuildRustls(err.to_string()))?;
1743
1744 let builder = match Self::client_cert_verifier(config, &provider)? {
1754 Some(verifier) => builder.with_client_cert_verifier(verifier),
1755 None => builder.with_no_client_auth(),
1756 };
1757
1758 let mut server_config = builder.with_cert_resolver(resolver);
1759 server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1760
1761 server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1762 DEFAULT_ALPN_PROTOCOLS
1763 .iter()
1764 .map(|p| p.as_bytes().to_vec())
1765 .collect()
1766 } else {
1767 config
1768 .alpn_protocols
1769 .iter()
1770 .map(|p| p.as_bytes().to_vec())
1771 .collect()
1772 };
1773
1774 Ok(server_config)
1775 }
1776
1777 fn check_crl_set(crls: &[CertificateRevocationListDer<'_>]) -> Result<(), ListenerError> {
1790 let mut scopes: Vec<(Vec<u8>, Option<Vec<u8>>)> = Vec::with_capacity(crls.len());
1791 for (index, crl) in crls.iter().enumerate() {
1792 let parsed = webpki::BorrowedCertRevocationList::from_der(crl.as_ref())
1793 .map_err(|e| ListenerError::ClientAuth(format!("invalid CRL: {e}")))?;
1794 let parsed = webpki::CertRevocationList::from(parsed);
1795 let issuer = parsed.issuer().to_vec();
1796 let distribution_point = parsed.issuing_distribution_point().map(<[u8]>::to_vec);
1797 let overlapping = scopes.iter().position(|(seen_issuer, seen_point)| {
1798 *seen_issuer == issuer
1799 && (seen_point.is_none()
1800 || distribution_point.is_none()
1801 || *seen_point == distribution_point)
1802 });
1803 if let Some(first) = overlapping {
1804 return Err(ListenerError::ClientAuth(format!(
1805 "CRLs #{} and #{} (counted across client_ca_crls) are issued by the \
1806 same CA for overlapping certificates, and only the first would be \
1807 consulted: configure one CRL per issuer, or one per distribution point",
1808 first + 1,
1809 index + 1
1810 )));
1811 }
1812 scopes.push((issuer, distribution_point));
1813 }
1814 Ok(())
1815 }
1816
1817 pub fn check_crls_current(crls: &[String], now: i64) -> Result<(), ListenerError> {
1829 let mut index = 0;
1830 for pem in crls {
1831 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1832 let crl =
1833 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?;
1834 index += 1;
1835 if let Some(next_update) = sozu_command::certificate::crl_next_update(crl.as_ref())
1836 && next_update <= now
1837 {
1838 return Err(ListenerError::ClientAuth(format!(
1839 "CRL #{index} (counted across client_ca_crls) expired {} seconds ago: \
1840 its nextUpdate is past, refresh it",
1841 now - next_update
1842 )));
1843 }
1844 }
1845 }
1846 Ok(())
1847 }
1848
1849 pub fn validate_client_auth_policy(policy: &ClientAuthPolicy) -> Result<(), ListenerError> {
1855 let mut config = HttpsListenerConfig::default();
1856 config.apply_client_auth_policy(policy);
1857 Self::client_cert_verifier(&config, &Arc::new(default_provider())).map(|_| ())
1858 }
1859
1860 fn client_cert_verifier(
1876 config: &HttpsListenerConfig,
1877 provider: &Arc<CryptoProvider>,
1878 ) -> Result<Option<Arc<dyn rustls::server::danger::ClientCertVerifier>>, ListenerError> {
1879 let raw = config
1881 .client_auth
1882 .unwrap_or(ClientAuthMode::ClientAuthNone as i32);
1883 let mode = ClientAuthMode::try_from(raw).map_err(|_| {
1884 ListenerError::ClientAuth(format!("unknown client_auth mode value {raw}"))
1885 })?;
1886 if mode == ClientAuthMode::ClientAuthNone {
1887 return Ok(None);
1888 }
1889
1890 let mut roots = RootCertStore::empty();
1897 for pem in &config.client_ca_certificates {
1898 let before = roots.len();
1899 for cert in CertificateDer::pem_slice_iter(pem.as_bytes()) {
1900 let cert =
1901 cert.map_err(|e| ListenerError::ClientAuth(format!("invalid CA PEM: {e}")))?;
1902 roots
1903 .add(cert)
1904 .map_err(|e| ListenerError::ClientAuth(format!("untrusted CA: {e}")))?;
1905 }
1906 if roots.len() == before {
1907 return Err(ListenerError::ClientAuth(
1908 "a configured trusted-CA entry contained no certificate".to_owned(),
1909 ));
1910 }
1911 }
1912 if roots.is_empty() {
1913 return Err(ListenerError::ClientAuth(
1914 "client auth requested but no trusted CA certificate was provided".to_owned(),
1915 ));
1916 }
1917
1918 let mut crls = Vec::new();
1923 for pem in &config.client_ca_crls {
1924 let before = crls.len();
1925 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1926 crls.push(
1927 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?,
1928 );
1929 }
1930 if crls.len() == before {
1931 return Err(ListenerError::ClientAuth(
1932 "a configured CRL entry contained no certificate revocation list".to_owned(),
1933 ));
1934 }
1935 }
1936 Self::check_crl_set(&crls)?;
1937
1938 let mut builder =
1939 WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone());
1940 if !crls.is_empty() {
1941 builder = builder.with_crls(crls).enforce_revocation_expiration();
1946 }
1947 if mode == ClientAuthMode::ClientAuthOptional {
1948 builder = builder.allow_unauthenticated();
1949 }
1950 let verifier = builder
1951 .build()
1952 .map_err(|e| ListenerError::ClientAuth(e.to_string()))?;
1953 Ok(Some(verifier))
1954 }
1955
1956 pub fn update_config(
1972 &mut self,
1973 patch: &UpdateHttpsListenerConfig,
1974 ) -> Result<(), ListenerError> {
1975 validate_h2_flood_knobs_https(patch)?;
1983 if let Some(ref alpn) = patch.alpn_protocols {
1984 validate_alpn_protocols(&alpn.values)?;
1985 }
1986 if let Some(ref hdr) = patch.sozu_id_header {
1987 validate_sozu_id_header(hdr)?;
1988 }
1989 if let Some(v) = patch.forwarded_headers {
1990 validate_forwarded_headers(v)?;
1991 }
1992 if patch.hsts.is_some_and(|hsts| hsts.enabled.is_none()) {
1999 return Err(ListenerError::HstsEnabledRequired);
2000 }
2001 HttpAnswers::validate_patch_templates(patch.http_answers.as_ref(), &patch.answers)
2002 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
2003
2004 let mut config = self.config.clone();
2005
2006 if let Some(v) = patch.public_address {
2008 config.public_address = Some(v);
2009 }
2010 if let Some(v) = patch.expect_proxy {
2011 config.expect_proxy = v;
2012 }
2013 if let Some(ref v) = patch.sticky_name {
2014 config.sticky_name = v.to_owned();
2015 }
2016 if let Some(v) = patch.front_timeout {
2017 config.front_timeout = v;
2018 }
2019 if let Some(v) = patch.back_timeout {
2020 config.back_timeout = v;
2021 }
2022 if let Some(v) = patch.connect_timeout {
2023 config.connect_timeout = v;
2024 }
2025 if let Some(v) = patch.request_timeout {
2026 config.request_timeout = v;
2027 }
2028 if let Some(v) = patch.strict_sni_binding {
2029 config.strict_sni_binding = Some(v);
2030 }
2031 if let Some(v) = patch.disable_http11 {
2032 config.disable_http11 = Some(v);
2033 }
2034 if let Some(ref v) = patch.sozu_id_header {
2035 config.sozu_id_header = Some(v.to_owned());
2036 }
2037 if let Some(v) = patch.elide_x_real_ip {
2038 config.elide_x_real_ip = Some(v);
2039 }
2040 if let Some(v) = patch.send_x_real_ip {
2041 config.send_x_real_ip = Some(v);
2042 }
2043 if let Some(v) = patch.forwarded_headers {
2044 config.forwarded_headers = Some(v);
2045 }
2046
2047 if let Some(v) = patch.h2_max_rst_stream_per_window {
2049 config.h2_max_rst_stream_per_window = Some(v);
2050 }
2051 if let Some(v) = patch.h2_max_ping_per_window {
2052 config.h2_max_ping_per_window = Some(v);
2053 }
2054 if let Some(v) = patch.h2_max_settings_per_window {
2055 config.h2_max_settings_per_window = Some(v);
2056 }
2057 if let Some(v) = patch.h2_max_empty_data_per_window {
2058 config.h2_max_empty_data_per_window = Some(v);
2059 }
2060 if let Some(v) = patch.h2_max_continuation_frames {
2061 config.h2_max_continuation_frames = Some(v);
2062 }
2063 if let Some(v) = patch.h2_max_glitch_count {
2064 config.h2_max_glitch_count = Some(v);
2065 }
2066 if let Some(v) = patch.h2_initial_connection_window {
2067 config.h2_initial_connection_window = Some(v);
2068 }
2069 if let Some(v) = patch.h2_max_concurrent_streams {
2070 config.h2_max_concurrent_streams = Some(v);
2071 }
2072 if let Some(v) = patch.h2_stream_shrink_ratio {
2073 config.h2_stream_shrink_ratio = Some(v);
2074 }
2075 if let Some(v) = patch.h2_max_rst_stream_lifetime {
2076 config.h2_max_rst_stream_lifetime = Some(v);
2077 }
2078 if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
2079 config.h2_max_rst_stream_abusive_lifetime = Some(v);
2080 }
2081 if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
2082 config.h2_max_rst_stream_emitted_lifetime = Some(v);
2083 }
2084 if let Some(v) = patch.h2_max_header_list_size {
2085 config.h2_max_header_list_size = Some(v);
2086 }
2087 if let Some(v) = patch.h2_max_header_table_size {
2088 config.h2_max_header_table_size = Some(v);
2089 }
2090 if let Some(v) = patch.h2_max_header_fields {
2091 config.h2_max_header_fields = Some(v);
2092 }
2093 if let Some(v) = patch.h2_stream_refusal_percent {
2094 config.h2_stream_refusal_percent = Some(v);
2095 }
2096 if let Some(v) = patch.h2_stream_idle_timeout_seconds {
2097 config.h2_stream_idle_timeout_seconds = Some(v);
2098 }
2099 if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
2100 config.h2_graceful_shutdown_deadline_seconds = Some(v);
2101 }
2102 if let Some(v) = patch.h2_max_window_update_stream0_per_window {
2103 config.h2_max_window_update_stream0_per_window = Some(v);
2104 }
2105
2106 if let Some(new_hsts) = patch.hsts {
2107 config.hsts = Some(new_hsts);
2108 }
2109 if let Some(ref alpn_wrapper) = patch.alpn_protocols {
2110 config.alpn_protocols = alpn_wrapper.values.clone();
2112 }
2113 if let Some(ref policy) = patch.client_auth_policy {
2114 config.apply_client_auth_policy(policy);
2115 }
2116
2117 let new_rustls = if patch.alpn_protocols.is_some() || patch.client_auth_policy.is_some() {
2129 Some(Arc::new(Self::create_rustls_context(
2130 &config,
2131 self.resolver.clone(),
2132 )?))
2133 } else {
2134 None
2135 };
2136
2137 let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
2142 let rebuilt = if answers_changed {
2143 if let Some(ref new_answers) = patch.http_answers {
2144 crate::sozu_command::state::merge_custom_http_answers(
2145 &mut config.http_answers,
2146 new_answers,
2147 );
2148 }
2149 for (code, body) in &patch.answers {
2150 if !body.is_empty() {
2151 config.answers.insert(code.clone(), body.clone());
2152 }
2153 }
2154
2155 let mut answers_map = config.answers.clone();
2156 if let Some(ref legacy) = config.http_answers {
2157 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
2158 }
2159 Some(
2160 HttpAnswers::new(&answers_map)
2161 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?,
2162 )
2163 } else {
2164 None
2165 };
2166
2167 self.config = config;
2169 if let Some(new_rustls) = new_rustls {
2170 self.rustls_details = new_rustls;
2171 }
2172 if let Some(alpn_wrapper) = &patch.alpn_protocols {
2173 debug_assert_eq!(
2178 self.config.alpn_protocols, alpn_wrapper.values,
2179 "committed ALPN config must match the patch values exactly"
2180 );
2181 }
2182 if let Some(mut rebuilt) = rebuilt {
2183 rebuilt.cluster_answers = self.answers.borrow().cluster_answers.clone();
2198 self.answers = Rc::new(RefCell::new(rebuilt));
2199 }
2200
2201 if patch.hsts.is_some() {
2218 let refreshed = self
2219 .fronts
2220 .refresh_inheriting_hsts(self.config.hsts.as_ref());
2221 for _ in 0..refreshed {
2222 crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
2223 }
2224 info!(
2225 "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
2226 frontend(s). Explicit per-frontend overrides untouched.",
2227 log_module_context!(),
2228 self.config.address,
2229 refreshed,
2230 );
2231 crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
2232 }
2233
2234 Ok(())
2235 }
2236
2237 pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2238 self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
2239 }
2240
2241 pub fn add_https_front_with_hsts_origin(
2248 &mut self,
2249 tls_front: HttpFrontend,
2250 hsts_origin: crate::router::HstsOrigin,
2251 ) -> Result<(), ListenerError> {
2252 self.fronts
2253 .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
2254 .map_err(ListenerError::AddFrontend)
2255 }
2256
2257 pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2258 debug!(
2259 "{} removing tls_front {:?}",
2260 log_module_context!(),
2261 tls_front
2262 );
2263 self.fronts
2264 .remove_http_front(&tls_front)
2265 .map_err(ListenerError::RemoveFrontend)
2266 }
2267
2268 fn accept(&mut self) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2269 if let Some(ref sock) = self.listener {
2270 match sock.accept() {
2271 Ok((frontend_sock, peer)) => {
2272 if self.nodelay_backlog
2273 && let Err(e) = frontend_sock.set_nodelay(true)
2274 {
2275 error!(
2276 "{} error setting nodelay on front socket({:?}): {:?}",
2277 log_module_context!(),
2278 frontend_sock,
2279 e
2280 );
2281 }
2282 Ok((frontend_sock, peer))
2283 }
2284 Err(e) if e.kind() == ErrorKind::WouldBlock => {
2285 self.nodelay_backlog = false;
2287 Err(AcceptError::WouldBlock)
2288 }
2289 Err(e) => {
2290 error!("{} accept() IO error: {:?}", log_module_context!(), e);
2291 Err(AcceptError::IoError)
2292 }
2293 }
2294 } else {
2295 error!(
2296 "{} cannot accept connections, no listening socket available",
2297 log_module_context!()
2298 );
2299 Err(AcceptError::IoError)
2300 }
2301 }
2302}
2303
2304pub struct HttpsProxy {
2305 listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
2306 clusters: HashMap<ClusterId, Cluster>,
2307 backends: Rc<RefCell<BackendMap>>,
2308 pool: Rc<RefCell<Pool>>,
2309 registry: Registry,
2310 sessions: Rc<RefCell<SessionManager>>,
2311}
2312
2313impl HttpsProxy {
2314 pub fn new(
2315 registry: Registry,
2316 sessions: Rc<RefCell<SessionManager>>,
2317 pool: Rc<RefCell<Pool>>,
2318 backends: Rc<RefCell<BackendMap>>,
2319 ) -> HttpsProxy {
2320 HttpsProxy {
2321 listeners: HashMap::new(),
2322 clusters: HashMap::new(),
2323 backends,
2324 pool,
2325 registry,
2326 sessions,
2327 }
2328 }
2329
2330 pub fn add_listener(
2331 &mut self,
2332 config: HttpsListenerConfig,
2333 token: Token,
2334 ) -> Result<Token, ProxyError> {
2335 if self.listeners.contains_key(&token) {
2336 return Err(ProxyError::ListenerAlreadyPresent);
2337 }
2338 let mut https_listener =
2339 HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
2340 if let Some(sibling) = self
2348 .listeners_at(https_listener.address)
2349 .ok()
2350 .and_then(|listeners| listeners.into_iter().next())
2351 {
2352 let sibling = sibling.borrow();
2353 https_listener.fronts = sibling.fronts.clone();
2354 https_listener
2358 .fronts
2359 .refresh_inheriting_hsts(https_listener.config.hsts.as_ref());
2360 https_listener.tags = sibling.tags.clone();
2361 let certificates = sibling
2362 .resolver
2363 .0
2364 .lock()
2365 .map_err(|e| ProxyError::Lock(e.to_string()))?
2366 .clone();
2367 *https_listener
2368 .resolver
2369 .0
2370 .lock()
2371 .map_err(|e| ProxyError::Lock(e.to_string()))? = certificates;
2372 }
2373 match self.listeners.entry(token) {
2374 Entry::Vacant(entry) => {
2375 entry.insert(Rc::new(RefCell::new(https_listener)));
2376 Ok(token)
2377 }
2378 _ => Err(ProxyError::ListenerAlreadyPresent),
2379 }
2380 }
2381
2382 pub fn remove_listener(
2383 &mut self,
2384 remove: RemoveListener,
2385 ) -> Result<Option<ResponseContent>, ProxyError> {
2386 let len = self.listeners.len();
2387
2388 let remove_address = remove.listener_key();
2389 self.listeners
2390 .retain(|_, listener| !listener.borrow().is(&remove_address));
2391
2392 if !self.listeners.len() < len {
2393 info!(
2394 "{} no HTTPS listener to remove at address {}",
2395 log_module_context!(),
2396 remove_address
2397 )
2398 }
2399 Ok(None)
2400 }
2401
2402 pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
2403 let listeners = std::mem::take(&mut self.listeners);
2404 let mut socket_errors = vec![];
2405 for l in listeners.values() {
2406 if let Some(mut sock) = l.borrow_mut().listener.take() {
2407 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2408 if let Err(e) = self.registry.deregister(&mut sock) {
2409 let error = format!("socket {sock:?}: {e:?}");
2410 socket_errors.push(error);
2411 }
2412 }
2413 }
2414
2415 if !socket_errors.is_empty() {
2416 return Err(ProxyError::SoftStop {
2417 proxy_protocol: "HTTPS".to_string(),
2418 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2419 });
2420 }
2421
2422 Ok(())
2423 }
2424
2425 pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
2426 let mut listeners = std::mem::take(&mut self.listeners);
2427 let mut socket_errors = vec![];
2428 for (_, l) in listeners.drain() {
2429 if let Some(mut sock) = l.borrow_mut().listener.take() {
2430 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2431 if let Err(e) = self.registry.deregister(&mut sock) {
2432 let error = format!("socket {sock:?}: {e:?}");
2433 socket_errors.push(error);
2434 }
2435 }
2436 }
2437
2438 if !socket_errors.is_empty() {
2439 return Err(ProxyError::HardStop {
2440 proxy_protocol: "HTTPS".to_string(),
2441 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2442 });
2443 }
2444
2445 Ok(())
2446 }
2447
2448 pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
2449 let certificates = self
2450 .listeners
2451 .values()
2452 .map(|listener| {
2453 let owned = listener.borrow();
2454 let resolver = owned
2459 .resolver
2460 .0
2461 .lock()
2462 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2463 let certificate_summaries = resolver
2464 .domains
2465 .to_hashmap()
2466 .drain()
2467 .map(|(k, fingerprint)| CertificateSummary {
2468 domain: certificate_summary_domain(&k),
2469 fingerprint: fingerprint.to_string(),
2470 })
2471 .collect();
2472
2473 Ok(CertificatesByAddress {
2474 address: owned.address.into(),
2475 certificate_summaries,
2476 })
2477 })
2478 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2479
2480 let listeners_count = certificates.len();
2481 let certificates_count = certificates
2482 .iter()
2483 .map(|entry| entry.certificate_summaries.len())
2484 .fold(0usize, |total, count| total.saturating_add(count));
2485
2486 info!(
2487 "{} got Certificates::All query, listeners_count={} certificates_count={}",
2488 log_module_context!(),
2489 listeners_count,
2490 certificates_count,
2491 );
2492
2493 Ok(Some(
2494 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2495 ))
2496 }
2497
2498 pub fn query_certificate_for_domain(
2499 &mut self,
2500 domain: String,
2501 ) -> Result<Option<ResponseContent>, ProxyError> {
2502 let domain_key = domain.to_ascii_lowercase();
2509 let certificates = self
2510 .listeners
2511 .values()
2512 .map(|listener| {
2513 let owned = listener.borrow();
2514 let resolver = owned
2517 .resolver
2518 .0
2519 .lock()
2520 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2521 let mut certificate_summaries = vec![];
2522
2523 if let Some((k, fingerprint)) = resolver.domain_lookup(domain_key.as_bytes(), true)
2524 {
2525 certificate_summaries.push(CertificateSummary {
2526 domain: certificate_summary_domain(k),
2527 fingerprint: fingerprint.to_string(),
2528 });
2529 }
2530 Ok(CertificatesByAddress {
2531 address: owned.address.into(),
2532 certificate_summaries,
2533 })
2534 })
2535 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2536
2537 let listeners_count = certificates.len();
2538 let certificates_count = certificates
2539 .iter()
2540 .map(|entry| entry.certificate_summaries.len())
2541 .fold(0usize, |total, count| total.saturating_add(count));
2542
2543 info!(
2544 "{} got Certificates::Domain query, domain_bytes={} listeners_count={} certificates_count={}",
2545 log_module_context!(),
2546 domain.len(),
2547 listeners_count,
2548 certificates_count,
2549 );
2550
2551 Ok(Some(
2552 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2553 ))
2554 }
2555
2556 pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2564 use crate::InheritedSocketFate;
2565 match self
2566 .listeners
2567 .values()
2568 .find(|listener| listener.borrow().is(addr))
2569 {
2570 None => InheritedSocketFate::Unclaimed,
2571 Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2572 Some(_) => InheritedSocketFate::Adopted,
2573 }
2574 }
2575
2576 pub fn activate_listener(
2577 &mut self,
2578 addr: &ListenerKey,
2579 tcp_listener: Option<MioTcpListener>,
2580 ) -> Result<Token, ProxyError> {
2581 let listener = self
2582 .listeners
2583 .values()
2584 .find(|listener| listener.borrow().is(addr))
2585 .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2586
2587 listener
2588 .borrow_mut()
2589 .activate(&self.registry, tcp_listener)
2590 .map_err(|listener_error| ProxyError::ListenerActivation {
2591 address: addr.address,
2592 listener_error,
2593 })
2594 }
2595
2596 pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
2605 self.listeners
2606 .iter()
2607 .find(|(_, listener)| listener.borrow().is(address))
2608 .map(|(token, _)| *token)
2609 }
2610
2611 pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
2612 self.listeners
2613 .values()
2614 .filter_map(|listener| {
2615 let mut owned = listener.borrow_mut();
2616 if let Some(listener) = owned.listener.take() {
2617 owned.active = false;
2620 return Some((owned.key(), listener));
2621 }
2622
2623 None
2624 })
2625 .collect()
2626 }
2627
2628 pub fn give_back_listener(
2629 &mut self,
2630 address: &ListenerKey,
2631 ) -> Result<(Token, MioTcpListener), ProxyError> {
2632 let listener = self
2633 .listeners
2634 .values()
2635 .find(|listener| listener.borrow().is(address))
2636 .ok_or(ProxyError::NoListenerFound(address.clone()))?;
2637
2638 let mut owned = listener.borrow_mut();
2639
2640 let taken_listener = owned
2641 .listener
2642 .take()
2643 .ok_or(ProxyError::UnactivatedListener)?;
2644
2645 owned.active = false;
2648
2649 Ok((owned.token, taken_listener))
2650 }
2651
2652 fn listeners_at(
2656 &self,
2657 address: StdSocketAddr,
2658 ) -> Result<Vec<Rc<RefCell<HttpsListener>>>, ProxyError> {
2659 let mut listeners: Vec<_> = self
2660 .listeners
2661 .iter()
2662 .filter(|(_, listener)| listener.borrow().address == address)
2663 .map(|(token, listener)| (*token, listener.clone()))
2664 .collect();
2665 listeners.sort_by_key(|(token, _)| *token);
2667 let listeners: Vec<_> = listeners
2668 .into_iter()
2669 .map(|(_, listener)| listener)
2670 .collect();
2671 if listeners.is_empty() {
2672 return Err(ProxyError::NoListenerFound(address.into()));
2673 }
2674 Ok(listeners)
2675 }
2676
2677 pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
2679 let address: std::net::SocketAddr = patch.address.into();
2680 let listener = self
2681 .listeners
2682 .values()
2683 .find(|l| l.borrow().address == address)
2684 .ok_or(ProxyError::NoListenerFound(address.into()))?;
2685 listener
2686 .borrow_mut()
2687 .update_config(&patch)
2688 .map_err(|listener_error| ProxyError::ListenerActivation {
2689 address,
2690 listener_error,
2691 })
2692 }
2693
2694 pub fn add_cluster(
2695 &mut self,
2696 mut cluster: Cluster,
2697 ) -> Result<Option<ResponseContent>, ProxyError> {
2698 let mut cluster_overrides = cluster.answers.clone();
2699 if let Some(answer_503) = cluster.answer_503.take() {
2700 cluster_overrides
2701 .entry("503".to_owned())
2702 .or_insert(answer_503);
2703 }
2704 if !cluster_overrides.is_empty() {
2705 for listener in self.listeners.values() {
2706 listener
2707 .borrow()
2708 .answers
2709 .borrow_mut()
2710 .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
2711 .map_err(|(status, error)| {
2712 ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
2713 })?;
2714 }
2715 }
2716 self.clusters
2717 .insert(cluster.cluster_id.as_str().into(), cluster);
2718 Ok(None)
2719 }
2720
2721 pub fn remove_cluster(
2722 &mut self,
2723 cluster_id: &str,
2724 ) -> Result<Option<ResponseContent>, ProxyError> {
2725 self.clusters.remove(cluster_id);
2726 for listener in self.listeners.values() {
2727 listener
2728 .borrow()
2729 .answers
2730 .borrow_mut()
2731 .remove_cluster_answers(cluster_id);
2732 }
2733
2734 Ok(None)
2735 }
2736
2737 pub fn add_https_frontend(
2738 &mut self,
2739 front: RequestHttpFrontend,
2740 ) -> Result<Option<ResponseContent>, ProxyError> {
2741 let front = front.clone().to_frontend().map_err(|request_error| {
2742 ProxyError::WrongInputFrontend {
2743 front: Box::new(front),
2744 error: request_error.to_string(),
2745 }
2746 })?;
2747
2748 for listener in self.listeners_at(front.address)? {
2749 let mut listener = listener.borrow_mut();
2750 let mut front = front.clone();
2751
2752 let hsts_origin = if front.hsts.is_none() {
2769 front.hsts = listener.config.hsts;
2770 crate::router::HstsOrigin::InheritedFromListenerDefault
2771 } else {
2772 crate::router::HstsOrigin::Explicit
2773 };
2774
2775 listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
2776 listener
2777 .add_https_front_with_hsts_origin(front, hsts_origin)
2778 .map_err(ProxyError::AddFrontend)?;
2779 }
2780 Ok(None)
2781 }
2782
2783 pub fn remove_https_frontend(
2784 &mut self,
2785 front: RequestHttpFrontend,
2786 ) -> Result<Option<ResponseContent>, ProxyError> {
2787 let front = front.clone().to_frontend().map_err(|request_error| {
2788 ProxyError::WrongInputFrontend {
2789 front: Box::new(front),
2790 error: request_error.to_string(),
2791 }
2792 })?;
2793
2794 let mut result = Ok(None);
2798 for listener in self.listeners_at(front.address)? {
2799 let mut listener = listener.borrow_mut();
2800 let hostname = front.hostname.to_owned();
2801
2802 if let Err(error) = listener.remove_https_front(front.clone())
2803 && result.is_ok()
2804 {
2805 result = Err(ProxyError::RemoveFrontend(error));
2806 }
2807
2808 if !listener.fronts.has_hostname(&hostname) {
2809 listener.set_tags(hostname, None);
2810 }
2811 }
2812 result
2813 }
2814
2815 pub fn add_certificate(
2816 &mut self,
2817 add_certificate: AddCertificate,
2818 ) -> Result<Option<ResponseContent>, ProxyError> {
2819 let address = add_certificate.address.into();
2820
2821 for listener in self.listeners_at(address)? {
2822 let listener = listener.borrow();
2823 let mut resolver = listener
2824 .resolver
2825 .0
2826 .lock()
2827 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2828
2829 resolver
2830 .add_certificate(&add_certificate)
2831 .map_err(ProxyError::AddCertificate)?;
2832 }
2833
2834 Ok(None)
2835 }
2836
2837 pub fn remove_certificate(
2839 &mut self,
2840 remove_certificate: RemoveCertificate,
2841 ) -> Result<Option<ResponseContent>, ProxyError> {
2842 let address = remove_certificate.address.into();
2843
2844 let fingerprint = Fingerprint(
2845 hex::decode(&remove_certificate.fingerprint)
2846 .map_err(ProxyError::WrongCertificateFingerprint)?,
2847 );
2848
2849 let mut result = Ok(None);
2853 for listener in self.listeners_at(address)? {
2854 let listener = listener.borrow();
2855 let applied = listener
2856 .resolver
2857 .0
2858 .lock()
2859 .map_err(|e| ProxyError::Lock(e.to_string()))
2860 .and_then(|mut resolver| {
2861 resolver
2862 .remove_certificate(&fingerprint)
2863 .map_err(ProxyError::RemoveCertificate)
2864 });
2865 if let Err(error) = applied
2866 && result.is_ok()
2867 {
2868 result = Err(error);
2869 }
2870 }
2871 result
2872 }
2873
2874 pub fn replace_certificate(
2876 &mut self,
2877 replace_certificate: ReplaceCertificate,
2878 ) -> Result<Option<ResponseContent>, ProxyError> {
2879 let address = replace_certificate.address.into();
2880
2881 let mut result = Ok(None);
2885 for listener in self.listeners_at(address)? {
2886 let listener = listener.borrow();
2887 let applied = listener
2888 .resolver
2889 .0
2890 .lock()
2891 .map_err(|e| ProxyError::Lock(e.to_string()))
2892 .and_then(|mut resolver| {
2893 resolver
2894 .replace_certificate(&replace_certificate)
2895 .map_err(ProxyError::ReplaceCertificate)
2896 });
2897 if let Err(error) = applied
2898 && result.is_ok()
2899 {
2900 result = Err(error);
2901 }
2902 }
2903 result
2904 }
2905}
2906
2907impl ProxyConfiguration for HttpsProxy {
2908 fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2909 match self.listeners.get(&Token(token.0)) {
2910 Some(listener) => listener.borrow_mut().accept(),
2911 None => Err(AcceptError::IoError),
2912 }
2913 }
2914
2915 fn create_session(
2916 &mut self,
2917 mut frontend_sock: MioTcpStream,
2918 peer: StdSocketAddr,
2919 token: ListenToken,
2920 wait_time: Duration,
2921 proxy: Rc<RefCell<Self>>,
2922 ) -> Result<(), AcceptError> {
2923 let listener = self
2924 .listeners
2925 .get(&Token(token.0))
2926 .ok_or(AcceptError::IoError)?;
2927 let owned = listener.borrow();
2932 let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
2933 error!(
2934 "{} failed to create server session: {:?}",
2935 log_module_context!(),
2936 e
2937 );
2938 AcceptError::IoError
2939 })?;
2940
2941 let mut session_manager = self.sessions.borrow_mut();
2942 let entry = session_manager.slab.vacant_entry();
2943 let session_token = Token(entry.key());
2944 debug_assert_eq!(
2949 session_token.0,
2950 entry.key(),
2951 "HTTPS session token must equal its slab key"
2952 );
2953
2954 self.registry
2955 .register(
2956 &mut frontend_sock,
2957 session_token,
2958 Interest::READABLE | Interest::WRITABLE,
2959 )
2960 .map_err(|register_error| {
2961 error!(
2962 "{} error registering front socket({:?}): {:?}",
2963 log_module_context!(),
2964 frontend_sock,
2965 register_error
2966 );
2967 AcceptError::RegisterError
2968 })?;
2969
2970 let public_address: StdSocketAddr = match owned.config.public_address {
2971 Some(pub_addr) => pub_addr.into(),
2972 None => owned.config.address.into(),
2973 };
2974
2975 let session = Rc::new(RefCell::new(HttpsSession::new(
2976 Duration::from_secs(owned.config.back_timeout as u64),
2977 Duration::from_secs(owned.config.connect_timeout as u64),
2978 Duration::from_secs(owned.config.front_timeout as u64),
2979 Duration::from_secs(owned.config.request_timeout as u64),
2980 owned.config.expect_proxy,
2981 listener.clone(),
2982 Rc::downgrade(&self.pool),
2983 proxy,
2984 public_address,
2985 rustls_details,
2986 frontend_sock,
2987 peer,
2988 session_token,
2989 wait_time,
2990 )));
2991 debug_assert_eq!(
2995 session.borrow().frontend_token(),
2996 session_token,
2997 "stored HTTPS session must report the slab token as its frontend token"
2998 );
2999 entry.insert(session);
3000
3001 Ok(())
3002 }
3003
3004 fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
3005 let request_id = request.id.clone();
3006
3007 let request_type = match request.content.request_type {
3008 Some(t) => t,
3009 None => return WorkerResponse::error(request_id, "Empty request"),
3010 };
3011
3012 let content_result = match request_type {
3013 RequestType::AddCluster(cluster) => {
3014 debug!(
3015 "{} {} add cluster {:?}",
3016 log_module_context!(),
3017 request_id,
3018 cluster
3019 );
3020 self.add_cluster(cluster)
3021 }
3022 RequestType::RemoveCluster(cluster_id) => {
3023 debug!(
3024 "{} {} remove cluster {:?}",
3025 log_module_context!(),
3026 request_id,
3027 cluster_id
3028 );
3029 self.remove_cluster(&cluster_id)
3030 }
3031 RequestType::AddHttpsFrontend(front) => {
3032 debug!(
3033 "{} {} add https front {:?}",
3034 log_module_context!(),
3035 request_id,
3036 front
3037 );
3038 self.add_https_frontend(front)
3039 }
3040 RequestType::RemoveHttpsFrontend(front) => {
3041 debug!(
3042 "{} {} remove https front {:?}",
3043 log_module_context!(),
3044 request_id,
3045 front
3046 );
3047 self.remove_https_frontend(front)
3048 }
3049 RequestType::AddCertificate(add_certificate) => {
3050 debug!(
3051 "{} {} add certificate: {:?}",
3052 log_module_context!(),
3053 request_id,
3054 add_certificate
3055 );
3056 self.add_certificate(add_certificate)
3057 }
3058 RequestType::RemoveCertificate(remove_certificate) => {
3059 debug!(
3060 "{} {} remove certificate: {:?}",
3061 log_module_context!(),
3062 request_id,
3063 remove_certificate
3064 );
3065 self.remove_certificate(remove_certificate)
3066 }
3067 RequestType::ReplaceCertificate(replace_certificate) => {
3068 debug!(
3069 "{} {} replace certificate: {:?}",
3070 log_module_context!(),
3071 request_id,
3072 replace_certificate
3073 );
3074 self.replace_certificate(replace_certificate)
3075 }
3076 RequestType::RemoveListener(remove) => {
3077 debug!(
3078 "{} removing HTTPS listener at address {:?}",
3079 log_module_context!(),
3080 remove.address
3081 );
3082 self.remove_listener(remove)
3083 }
3084 RequestType::SoftStop(_) => {
3085 debug!(
3086 "{} {} processing soft shutdown",
3087 log_module_context!(),
3088 request_id
3089 );
3090 match self.soft_stop() {
3091 Ok(_) => {
3092 info!(
3093 "{} {} soft stop successful",
3094 log_module_context!(),
3095 request_id
3096 );
3097 return WorkerResponse::processing(request.id);
3098 }
3099 Err(e) => Err(e),
3100 }
3101 }
3102 RequestType::HardStop(_) => {
3103 debug!(
3104 "{} {} processing hard shutdown",
3105 log_module_context!(),
3106 request_id
3107 );
3108 match self.hard_stop() {
3109 Ok(_) => {
3110 debug!(
3111 "{} {} hard stop successful",
3112 log_module_context!(),
3113 request_id
3114 );
3115 return WorkerResponse::processing(request.id);
3116 }
3117 Err(e) => Err(e),
3118 }
3119 }
3120 RequestType::Status(_) => {
3121 debug!("{} {} status", log_module_context!(), request_id);
3122 Ok(None)
3123 }
3124 RequestType::QueryCertificatesFromWorkers(filters) => {
3125 if let Some(domain) = filters.domain {
3126 debug!(
3127 "{} {} query certificate for domain_bytes={}",
3128 log_module_context!(),
3129 request_id,
3130 domain.len(),
3131 );
3132 self.query_certificate_for_domain(domain)
3133 } else {
3134 debug!(
3135 "{} {} query all certificates",
3136 log_module_context!(),
3137 request_id
3138 );
3139 self.query_all_certificates()
3140 }
3141 }
3142 other_request => {
3143 debug!(
3144 "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
3145 log_module_context!(),
3146 request.id,
3147 other_request
3148 );
3149 Err(ProxyError::UnsupportedMessage)
3150 }
3151 };
3152
3153 match content_result {
3154 Ok(content) => {
3155 debug!("{} {} successful", log_module_context!(), request_id);
3156 match content {
3157 Some(content) => WorkerResponse::ok_with_content(request_id, content),
3158 None => WorkerResponse::ok(request_id),
3159 }
3160 }
3161 Err(proxy_error) => {
3162 debug!(
3163 "{} {} unsuccessful: {}",
3164 log_module_context!(),
3165 request_id,
3166 proxy_error
3167 );
3168 WorkerResponse::error(request_id, proxy_error)
3169 }
3170 }
3171 }
3172}
3173impl L7Proxy for HttpsProxy {
3174 fn kind(&self) -> ListenerType {
3175 ListenerType::Https
3176 }
3177
3178 fn register_socket(
3179 &self,
3180 socket: &mut MioTcpStream,
3181 token: Token,
3182 interest: Interest,
3183 ) -> Result<(), std::io::Error> {
3184 self.registry.register(socket, token, interest)
3185 }
3186
3187 fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
3188 let mut session_manager = self.sessions.borrow_mut();
3189 let entry = session_manager.slab.vacant_entry();
3190 let token = Token(entry.key());
3191 let _entry = entry.insert(session);
3192 token
3193 }
3194
3195 fn remove_session(&self, token: Token) -> bool {
3196 let mut sessions = self.sessions.borrow_mut();
3197 sessions.untrack_all_cluster_ip(token);
3200 sessions.slab.try_remove(token.0).is_some()
3201 }
3202
3203 fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
3204 &self.clusters
3205 }
3206
3207 fn sessions(&self) -> Rc<RefCell<SessionManager>> {
3208 self.sessions.clone()
3209 }
3210}
3211
3212fn rustls_version_str(version: ProtocolVersion) -> &'static str {
3214 match version {
3215 ProtocolVersion::SSLv2 => "tls.version.SSLv2",
3216 ProtocolVersion::SSLv3 => "tls.version.SSLv3",
3217 ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
3218 ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
3219 ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
3220 ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
3221 ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
3222 ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
3223 ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
3224 ProtocolVersion::Unknown(_) => "tls.version.Unknown",
3225 _ => "tls.version.unimplemented",
3226 }
3227}
3228
3229pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
3236 match version {
3237 ProtocolVersion::SSLv2 => Some("SSLv2"),
3238 ProtocolVersion::SSLv3 => Some("SSLv3"),
3239 ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
3240 ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
3241 ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
3242 ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
3243 ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
3244 ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
3245 ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
3246 _ => None,
3247 }
3248}
3249
3250fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
3252 match cipher.suite() {
3253 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3254 "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
3255 }
3256 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3257 "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
3258 }
3259 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3260 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
3261 }
3262 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3263 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
3264 }
3265 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3266 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
3267 }
3268 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3269 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
3270 }
3271 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
3272 CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
3273 CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
3274 _ => "tls.cipher.Unsupported",
3275 }
3276}
3277
3278pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
3285 match cipher.suite() {
3286 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3287 Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
3288 }
3289 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3290 Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
3291 }
3292 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3293 Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
3294 }
3295 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3296 Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
3297 }
3298 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3299 Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
3300 }
3301 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3302 Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
3303 }
3304 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
3305 CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
3306 CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
3307 _ => None,
3308 }
3309}
3310
3311pub mod testing {
3312 use crate::testing::*;
3313
3314 pub fn start_https_worker(
3316 config: HttpsListenerConfig,
3317 channel: ProxyChannel,
3318 max_buffers: usize,
3319 buffer_size: usize,
3320 ) -> anyhow::Result<()> {
3321 let address = config.address.into();
3322
3323 let ServerParts {
3324 event_loop,
3325 registry,
3326 sessions,
3327 pool,
3328 backends,
3329 client_scm_socket: _,
3330 server_scm_socket,
3331 server_config,
3332 } = prebuild_server(max_buffers, buffer_size, true)?;
3333
3334 let token = {
3335 let mut sessions = sessions.borrow_mut();
3336 let entry = sessions.slab.vacant_entry();
3337 let key = entry.key();
3338 let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3339 protocol: Protocol::HTTPSListen,
3340 })));
3341 Token(key)
3342 };
3343
3344 let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3345 proxy
3346 .add_listener(config, token)
3347 .with_context(|| "Failed at creating adding the listener")?;
3348 proxy
3349 .activate_listener(&address, None)
3350 .with_context(|| "Failed at creating activating the listener")?;
3351
3352 let mut server = Server::new(
3353 event_loop,
3354 channel,
3355 server_scm_socket,
3356 sessions,
3357 pool,
3358 backends,
3359 None,
3360 Some(proxy),
3361 None,
3362 server_config,
3363 None,
3364 false,
3365 )
3366 .with_context(|| "Failed at creating server")?;
3367
3368 debug!("{} starting event loop", log_module_context!());
3369 server.run();
3370 debug!("{} ending event loop", log_module_context!());
3371 Ok(())
3372 }
3373}
3374
3375#[cfg(test)]
3376mod tests {
3377 use std::sync::Arc;
3378
3379 use sozu_command::{
3380 config::ListenerBuilder,
3381 proto::command::{CertificateAndKey, SocketAddress},
3382 };
3383
3384 use super::*;
3385 use crate::{
3386 router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode},
3387 socket::SocketHandler,
3388 };
3389
3390 #[test]
3395 fn the_upgraded_frontend_reads_only_when_a_read_is_due() {
3396 assert_eq!(
3397 upgraded_frontend_events(Ready::EMPTY, false),
3398 Ready::WRITABLE,
3399 "a drained socket and an empty rustls need no read"
3400 );
3401 assert_eq!(
3402 upgraded_frontend_events(Ready::WRITABLE, false),
3403 Ready::WRITABLE
3404 );
3405 assert_eq!(
3406 upgraded_frontend_events(Ready::READABLE, false),
3407 Ready::READABLE | Ready::WRITABLE,
3408 "an edge not yet read must survive the upgrade"
3409 );
3410 assert_eq!(
3411 upgraded_frontend_events(Ready::EMPTY, true),
3412 Ready::READABLE | Ready::WRITABLE,
3413 "plaintext rustls already holds raises no edge"
3414 );
3415 assert_eq!(
3416 upgraded_frontend_events(Ready::READABLE | Ready::HUP, true),
3417 Ready::READABLE | Ready::WRITABLE | Ready::HUP,
3418 "a half-close seen during the handshake must reach the mux"
3419 );
3420 assert_eq!(
3421 upgraded_frontend_events(Ready::HUP, false),
3422 Ready::WRITABLE | Ready::HUP,
3423 "HUP survives even when no read is due"
3424 );
3425 assert_eq!(
3426 upgraded_frontend_events(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED, true),
3427 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::WRITE_CLOSED,
3428 "a hang-up seen during the handshake must reach the mux as one"
3429 );
3430 }
3431
3432 #[test]
3433 fn successful_tls_handshake_log_bounds_sni_and_alpn() {
3434 const SNI_SECRET: &str = "HTTPS_HANDSHAKE_SNI_SECRET_SENTINEL";
3435 const ALPN_SECRET: &str = "HTTPS_HANDSHAKE_ALPN_SECRET_SENTINEL";
3436
3437 let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
3438 let alpn = format!("{ALPN_SECRET}{}", "x".repeat(4096));
3439 let sni_len = sni.len();
3440 let alpn_len = alpn.len();
3441 let output = successful_tls_handshake_summary(Some(&sni), Some(&alpn));
3442
3443 for secret in [SNI_SECRET, ALPN_SECRET] {
3444 assert!(
3445 !output.contains(secret),
3446 "successful TLS handshake log leaked {secret}: {output}"
3447 );
3448 }
3449 for metadata in [
3450 format!("sni_bytes=Some({sni_len})"),
3451 format!("alpn_bytes=Some({alpn_len})"),
3452 ] {
3453 assert!(
3454 output.contains(&metadata),
3455 "successful TLS handshake log omitted {metadata}: {output}"
3456 );
3457 }
3458 assert!(
3459 output.len() <= 512,
3460 "successful TLS handshake log is not bounded: {} bytes",
3461 output.len()
3462 );
3463 }
3464
3465 fn proxy_with_certificate_domain(domain: String) -> HttpsProxy {
3466 let crate::testing::ServerParts {
3467 registry,
3468 sessions,
3469 pool,
3470 backends,
3471 ..
3472 } = crate::testing::prebuild_server(16, 16_384, false)
3473 .expect("test HTTPS proxy dependencies must initialize");
3474 let address = SocketAddress::new_v4(127, 0, 0, 1, crate::testing::provide_port());
3475 let mut config = ListenerBuilder::new_https(address)
3476 .to_tls(None)
3477 .expect("test HTTPS listener config must build");
3478 config.cipher_list = vec!["TLS13_AES_256_GCM_SHA384".to_owned()];
3479 config.groups_list = vec!["P-256".to_owned()];
3480 let mut proxy = HttpsProxy::new(registry, sessions, pool, backends);
3481 proxy
3482 .add_listener(config, Token(10))
3483 .expect("test HTTPS listener must be added");
3484 proxy
3485 .add_certificate(AddCertificate {
3486 address,
3487 certificate: CertificateAndKey {
3488 certificate: include_str!("../assets/certificate.pem").to_owned(),
3489 key: include_str!("../assets/key.pem").to_owned(),
3490 certificate_chain: Vec::new(),
3491 versions: Vec::new(),
3492 names: vec![domain],
3493 },
3494 expired_at: None,
3495 })
3496 .expect("test certificate must be added");
3497 proxy
3498 }
3499
3500 #[test]
3514 fn a_poisoned_resolver_fails_the_certificate_queries_instead_of_the_worker() {
3515 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3516
3517 let resolver = proxy
3518 .listeners
3519 .values()
3520 .next()
3521 .expect("the test proxy has one listener")
3522 .borrow()
3523 .resolver()
3524 .clone();
3525
3526 let poisoner = resolver.clone();
3530 let unwound = std::thread::spawn(move || {
3531 let _guard = poisoner.0.lock().expect("first lock is not poisoned");
3532 panic!("simulated worker panic while holding the resolver lock");
3533 })
3534 .join();
3535 assert!(unwound.is_err(), "the poisoning thread must have unwound");
3536 assert!(
3537 resolver.0.lock().is_err(),
3538 "the resolver mutex must now be poisoned"
3539 );
3540
3541 assert!(
3542 matches!(proxy.query_all_certificates(), Err(ProxyError::Lock(_))),
3543 "Certificates::All on a poisoned resolver must report a lock error"
3544 );
3545 assert!(
3546 matches!(
3547 proxy.query_certificate_for_domain("lolcatho.st".to_owned()),
3548 Err(ProxyError::Lock(_))
3549 ),
3550 "Certificates::Domain on a poisoned resolver must report a lock error"
3551 );
3552 }
3553
3554 #[test]
3569 fn a_non_utf8_resolver_key_is_listed_lossily_not_panicked() {
3570 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3571
3572 let resolver = proxy
3573 .listeners
3574 .values()
3575 .next()
3576 .expect("the test proxy has one listener")
3577 .borrow()
3578 .resolver()
3579 .clone();
3580 {
3581 let mut owned = resolver.0.lock().expect("resolver lock");
3582 let inserted = owned
3583 .domains
3584 .insert(vec![0xff, 0xfe], Fingerprint(vec![0x01, 0x02]));
3585 assert_ne!(
3586 inserted,
3587 crate::router::pattern_trie::InsertResult::Failed,
3588 "the SNI trie must accept the non-UTF-8 key this test relies on"
3589 );
3590 }
3591
3592 let response = proxy
3593 .query_all_certificates()
3594 .expect("a non-UTF-8 trie key must not fail the query");
3595 assert!(
3596 response.is_some(),
3597 "Certificates::All must still answer with a listing"
3598 );
3599 }
3600
3601 #[test]
3602 fn query_all_certificates_log_redacts_response_domains() {
3603 const DOMAIN_SECRET: &str = "QUERY_ALL_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3604
3605 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3609 let output = crate::capture_test_logs(move || {
3610 let mut proxy = proxy_with_certificate_domain(domain);
3611 proxy
3612 .query_all_certificates()
3613 .expect("certificate query must succeed");
3614 });
3615
3616 assert!(
3617 !output.contains(DOMAIN_SECRET),
3618 "Certificates::All log leaked response domain {DOMAIN_SECRET}"
3619 );
3620 for metadata in ["listeners_count=1", "certificates_count=1"] {
3621 assert!(
3622 output.contains(metadata),
3623 "Certificates::All log omitted bounded metadata {metadata}: {output}"
3624 );
3625 }
3626 assert!(
3627 output.len() <= 1024,
3628 "Certificates::All log capture is not bounded: {} bytes",
3629 output.len()
3630 );
3631 }
3632
3633 #[test]
3634 fn query_certificate_for_domain_log_redacts_request_and_response_domain() {
3635 const DOMAIN_SECRET: &str = "QUERY_ONE_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3636
3637 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3640 let domain_len = domain.len();
3641 let output = crate::capture_test_logs_at_level("debug", move || {
3642 let mut proxy = proxy_with_certificate_domain(domain.clone());
3643 let response = proxy.notify(WorkerRequest {
3644 id: "query-domain-redaction-test".to_owned(),
3645 content: RequestType::QueryCertificatesFromWorkers(
3646 sozu_command::proto::command::QueryCertificatesFilters {
3647 domain: Some(domain),
3648 fingerprint: None,
3649 },
3650 )
3651 .into(),
3652 });
3653 assert_eq!(
3654 response.status,
3655 sozu_command::proto::command::ResponseStatus::Ok as i32,
3656 "domain certificate query must succeed: {}",
3657 response.message
3658 );
3659 });
3660
3661 assert!(
3662 !output.contains(DOMAIN_SECRET),
3663 "Certificates::Domain log leaked request or response domain {DOMAIN_SECRET}"
3664 );
3665 for metadata in [
3666 format!("domain_bytes={domain_len}"),
3667 "listeners_count=1".to_owned(),
3668 "certificates_count=1".to_owned(),
3669 ] {
3670 assert!(
3671 output.contains(&metadata),
3672 "Certificates::Domain log omitted bounded metadata {metadata}: {output}"
3673 );
3674 }
3675 assert!(
3676 output.len() <= 1024,
3677 "Certificates::Domain log capture is not bounded: {} bytes",
3678 output.len()
3679 );
3680 }
3681
3682 #[test]
3683 fn frontend_from_request_test() {
3684 let cluster_id1 = ClusterId::from("cluster_1");
3685 let cluster_id2 = ClusterId::from("cluster_2");
3686 let cluster_id3 = ClusterId::from("cluster_3");
3687 let uri1 = "/".to_owned();
3688 let uri2 = "/yolo".to_owned();
3689 let uri3 = "/yolo/swag".to_owned();
3690
3691 let mut fronts = Router::new();
3692 assert!(fronts.add_tree_rule(
3693 "lolcatho.st".as_bytes(),
3694 &PathRule::Prefix(uri1),
3695 &MethodRule::new(None),
3696 &Route::ClusterId(cluster_id1.clone())
3697 ));
3698 assert!(fronts.add_tree_rule(
3699 "lolcatho.st".as_bytes(),
3700 &PathRule::Prefix(uri2),
3701 &MethodRule::new(None),
3702 &Route::ClusterId(cluster_id2)
3703 ));
3704 assert!(fronts.add_tree_rule(
3705 "lolcatho.st".as_bytes(),
3706 &PathRule::Prefix(uri3),
3707 &MethodRule::new(None),
3708 &Route::ClusterId(cluster_id3)
3709 ));
3710 assert!(fronts.add_tree_rule(
3711 "other.domain".as_bytes(),
3712 &PathRule::Prefix("test".to_string()),
3713 &MethodRule::new(None),
3714 &Route::ClusterId(cluster_id1)
3715 ));
3716
3717 let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
3718 let resolver = Arc::new(MutexCertificateResolver::default());
3719
3720 let crypto_provider = Arc::new(default_provider());
3721
3722 let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
3723 .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
3724 .expect("could not create rustls config server")
3725 .with_no_client_auth()
3726 .with_cert_resolver(resolver.clone());
3727
3728 let rustls_details = Arc::new(server_config);
3729
3730 let default_config = ListenerBuilder::new_https(address)
3731 .to_tls(None)
3732 .expect("Could not create default HTTPS listener config");
3733
3734 println!("it doesn't even matter");
3735
3736 let listener = HttpsListener {
3737 parked_listener: None,
3738 nodelay_backlog: false,
3739 listener: None,
3740 address: address.into(),
3741 fronts,
3742 rustls_details,
3743 resolver,
3744 answers: Rc::new(RefCell::new(
3745 HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
3746 )),
3747 config: default_config,
3748 token: Token(0),
3749 active: true,
3750 tags: BTreeMap::new(),
3751 };
3752
3753 println!("TEST {}", line!());
3754 let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get);
3755 assert_eq!(
3756 frontend1
3757 .expect("should find a frontend")
3758 .cluster_id
3759 .as_deref(),
3760 Some("cluster_1")
3761 );
3762 println!("TEST {}", line!());
3763 let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get);
3764 assert_eq!(
3765 frontend2
3766 .expect("should find a frontend")
3767 .cluster_id
3768 .as_deref(),
3769 Some("cluster_1")
3770 );
3771 println!("TEST {}", line!());
3772 let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get);
3773 assert_eq!(
3774 frontend3
3775 .expect("should find a frontend")
3776 .cluster_id
3777 .as_deref(),
3778 Some("cluster_2")
3779 );
3780 println!("TEST {}", line!());
3781 let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get);
3782 assert_eq!(
3783 frontend4
3784 .expect("should find a frontend")
3785 .cluster_id
3786 .as_deref(),
3787 Some("cluster_3")
3788 );
3789 println!("TEST {}", line!());
3790 let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get);
3791 assert!(frontend5.is_err());
3792 }
3794
3795 #[test]
3796 fn wildcard_certificate_names() {
3797 let mut trie = TrieNode::root();
3798
3799 trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
3800 trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
3801 trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
3802 trie.domain_insert(
3803 "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
3804 3u8,
3805 );
3806 trie.domain_insert(
3807 "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
3808 4u8,
3809 );
3810
3811 let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
3812 println!("query result: {res:?}");
3813
3814 assert_eq!(
3815 trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
3816 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3817 );
3818 assert_eq!(
3819 trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
3820 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3821 );
3822 }
3823
3824 #[test]
3825 fn wildcard_with_subdomains() {
3826 let mut trie = TrieNode::root();
3827
3828 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3829 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3830
3831 let res = trie.domain_lookup(b"sub.test.example.com", true);
3832 println!("query result: {res:?}");
3833
3834 assert_eq!(
3835 trie.domain_lookup(b"sub.test.example.com", true),
3836 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3837 );
3838 assert_eq!(
3839 trie.domain_lookup(b"hello.sub.test.example.com", true),
3840 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3841 );
3842
3843 let mut trie = TrieNode::root();
3845
3846 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3847 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3848
3849 let res = trie.domain_lookup(b"sub.test.example.com", true);
3850 println!("query result: {res:?}");
3851
3852 assert_eq!(
3853 trie.domain_lookup(b"sub.test.example.com", true),
3854 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3855 );
3856 assert_eq!(
3857 trie.domain_lookup(b"hello.sub.test.example.com", true),
3858 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3859 );
3860 }
3861
3862 #[test]
3865 fn a_listener_with_an_unknown_forwarded_headers_value_is_refused() {
3866 let build = |forwarded_headers: Option<i32>| {
3867 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1043))
3868 .to_tls(None)
3869 .expect("default HTTPS listener config");
3870 cfg.forwarded_headers = forwarded_headers;
3871 HttpsListener::try_new(cfg, Token(0))
3872 };
3873 assert!(build(Some(-1)).is_err(), "an unknown mode must be refused");
3874 let listener = build(Some(ForwardedHeaders::None as i32)).expect("a known mode");
3875 assert_eq!(listener.get_forwarded_headers(), ForwardedHeaders::None);
3876 }
3877
3878 #[test]
3884 fn get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold() {
3885 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1042))
3886 .to_tls(None)
3887 .expect("default HTTPS listener config");
3888 cfg.h2_max_rst_stream_per_window = Some(101);
3889 cfg.h2_max_ping_per_window = Some(102);
3890 cfg.h2_max_settings_per_window = Some(103);
3891 cfg.h2_max_empty_data_per_window = Some(104);
3892 cfg.h2_max_window_update_stream0_per_window = Some(105);
3893 cfg.h2_max_continuation_frames = Some(106);
3894 cfg.h2_max_glitch_count = Some(107);
3895 cfg.h2_max_rst_stream_lifetime = Some(108);
3896 cfg.h2_max_rst_stream_abusive_lifetime = Some(109);
3897 cfg.h2_max_rst_stream_emitted_lifetime = Some(110);
3898 cfg.h2_max_header_list_size = Some(111);
3899 cfg.h2_max_header_table_size = Some(112);
3900 cfg.h2_max_header_fields = Some(113);
3901 cfg.h2_stream_refusal_percent = Some(114);
3902
3903 let listener = HttpsListener::try_new(cfg, Token(0)).expect("build listener");
3904 let flood = listener.get_h2_flood_config();
3905
3906 assert_eq!(flood.max_rst_stream_per_window(), 101);
3907 assert_eq!(flood.max_ping_per_window(), 102);
3908 assert_eq!(flood.max_settings_per_window(), 103);
3909 assert_eq!(flood.max_empty_data_per_window(), 104);
3910 assert_eq!(flood.max_window_update_stream0_per_window(), 105);
3911 assert_eq!(flood.max_continuation_frames(), 106);
3912 assert_eq!(flood.max_glitch_count(), 107);
3913 assert_eq!(flood.max_rst_stream_lifetime(), 108);
3914 assert_eq!(flood.max_rst_stream_abusive_lifetime(), 109);
3915 assert_eq!(flood.max_rst_stream_emitted_lifetime(), 110);
3916 assert_eq!(flood.max_header_list_size(), 111);
3917 assert_eq!(flood.max_header_table_size(), 112);
3918 assert_eq!(flood.max_header_fields(), 113);
3919 assert_eq!(flood.stream_refusal_percent(), 114);
3920 }
3921
3922 #[test]
3923 fn h2_stream_idle_timeout_inherits_back_timeout() {
3924 use std::time::Duration;
3925
3926 let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
3927 let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
3928 let mut cfg = ListenerBuilder::new_https(address)
3929 .to_tls(None)
3930 .expect("default HTTPS listener config");
3931 cfg.back_timeout = back_timeout;
3932 cfg.h2_stream_idle_timeout_seconds = explicit;
3933 HttpsListener::try_new(cfg, Token(0)).expect("build listener")
3934 };
3935
3936 assert_eq!(
3938 build(180, None).get_h2_stream_idle_timeout(),
3939 Duration::from_secs(180)
3940 );
3941
3942 assert_eq!(
3944 build(5, None).get_h2_stream_idle_timeout(),
3945 Duration::from_secs(30)
3946 );
3947
3948 assert_eq!(
3950 build(180, Some(10)).get_h2_stream_idle_timeout(),
3951 Duration::from_secs(10)
3952 );
3953 assert_eq!(
3954 build(5, Some(600)).get_h2_stream_idle_timeout(),
3955 Duration::from_secs(600)
3956 );
3957
3958 assert_eq!(
3960 build(180, Some(0)).get_h2_stream_idle_timeout(),
3961 Duration::from_secs(1)
3962 );
3963 }
3964
3965 const PROXY_ADVERTISED_PEER: &str = "10.0.0.42:12345";
3970
3971 #[test]
4002 fn front_rustls_peer_snapshot_is_the_session_peer_not_the_accepted_socket() {
4003 let advertised: StdSocketAddr = PROXY_ADVERTISED_PEER
4004 .parse()
4005 .expect("the advertised peer literal must parse");
4006
4007 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4008 let listener = proxy
4009 .listeners
4010 .values()
4011 .next()
4012 .expect("the test proxy has one listener")
4013 .clone();
4014 let pool = Rc::downgrade(&proxy.pool);
4015 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4016 .expect("a test rustls server session must be created");
4017 let public_address = *listener.borrow().get_addr();
4018 let proxy = Rc::new(RefCell::new(proxy));
4019
4020 let balancer = std::net::TcpListener::bind("127.0.0.1:0")
4025 .expect("test listener must bind to a loopback port");
4026 let balancer_peer = balancer
4027 .local_addr()
4028 .expect("test listener must report its local address");
4029 let stream =
4030 std::net::TcpStream::connect(balancer_peer).expect("loopback connect must complete");
4031 stream
4032 .set_nonblocking(true)
4033 .expect("mio requires a nonblocking stream");
4034 let stream = MioTcpStream::from_std(stream);
4035
4036 let mut session = HttpsSession::new(
4037 Duration::from_secs(30),
4038 Duration::from_secs(30),
4039 Duration::from_secs(30),
4040 Duration::from_secs(30),
4041 false,
4044 listener,
4045 pool,
4046 proxy,
4047 public_address,
4048 rustls_details,
4049 stream,
4050 balancer_peer,
4053 Token(0),
4054 Duration::from_secs(0),
4055 );
4056
4057 assert_eq!(
4058 session.peer_address,
4059 Some(balancer_peer),
4060 "a direct-route session starts out with the accepted socket's peer"
4061 );
4062
4063 let mut handshake = match session.state.take() {
4064 HttpsStateMachine::Handshake(handshake) => handshake,
4065 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4066 };
4067
4068 session.peer_address = Some(advertised);
4070 handshake.peer_address = Some(advertised);
4071 assert_ne!(
4072 advertised, balancer_peer,
4073 "the advertised and connection peers must differ for this test to discriminate"
4074 );
4075
4076 let upgraded = session
4077 .upgrade_handshake(handshake)
4078 .expect("the handshake must upgrade to a mux session");
4079 let (snapshot, live_lookup) = match &upgraded {
4082 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4083 mux::Connection::H1(connection) => (
4084 connection.socket.peer_addr(),
4085 connection.socket.socket_ref().peer_addr().ok(),
4086 ),
4087 mux::Connection::H2(connection) => (
4088 connection.socket.peer_addr(),
4089 connection.socket.socket_ref().peer_addr().ok(),
4090 ),
4091 },
4092 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4093 };
4094
4095 assert_eq!(
4098 live_lookup,
4099 Some(balancer_peer),
4100 "the frontend socket must still be connected to the stand-in balancer"
4101 );
4102 assert_eq!(
4103 snapshot,
4104 Some(advertised),
4105 "FrontRustls must carry the PROXY-advertised source, not the accepted socket's peer"
4106 );
4107 }
4108
4109 #[test]
4127 fn front_rustls_peer_addr_falls_back_to_the_live_lookup() {
4128 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4129 let listener = proxy
4130 .listeners
4131 .values()
4132 .next()
4133 .expect("the test proxy has one listener")
4134 .clone();
4135 let pool = Rc::downgrade(&proxy.pool);
4136 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4137 .expect("a test rustls server session must be created");
4138 let public_address = *listener.borrow().get_addr();
4139 let proxy = Rc::new(RefCell::new(proxy));
4140
4141 let peer = std::net::TcpListener::bind("127.0.0.1:0")
4142 .expect("test listener must bind to a loopback port");
4143 let live_peer = peer
4144 .local_addr()
4145 .expect("test listener must report its local address");
4146 let stream =
4147 std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
4148 stream
4149 .set_nonblocking(true)
4150 .expect("mio requires a nonblocking stream");
4151 let stream = MioTcpStream::from_std(stream);
4152
4153 let mut session = HttpsSession::new(
4154 Duration::from_secs(30),
4155 Duration::from_secs(30),
4156 Duration::from_secs(30),
4157 Duration::from_secs(30),
4158 false,
4159 listener,
4160 pool,
4161 proxy,
4162 public_address,
4163 rustls_details,
4164 stream,
4165 live_peer,
4166 Token(0),
4167 Duration::from_secs(0),
4168 );
4169
4170 let mut handshake = match session.state.take() {
4171 HttpsStateMachine::Handshake(handshake) => handshake,
4172 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4173 };
4174
4175 session.peer_address = None;
4177 handshake.peer_address = None;
4178
4179 let upgraded = session
4180 .upgrade_handshake(handshake)
4181 .expect("the handshake must upgrade to a mux session");
4182 let snapshot = match &upgraded {
4183 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4184 mux::Connection::H1(connection) => connection.socket.peer_addr(),
4185 mux::Connection::H2(connection) => connection.socket.peer_addr(),
4186 },
4187 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4188 };
4189
4190 assert_eq!(
4191 snapshot,
4192 Some(live_peer),
4193 "with nothing cached, FrontRustls::peer_addr must degrade to getpeername(2)"
4194 );
4195 }
4196
4197 #[test]
4214 fn query_certificate_for_domain_answers_any_spelling_of_the_name() {
4215 let mut proxy = proxy_with_certificate_domain("MiXeD.Example.COM".to_owned());
4216
4217 let mut summaries_for = |query: &str| {
4218 let response = proxy
4219 .query_certificate_for_domain(query.to_owned())
4220 .expect("the domain certificate query must succeed")
4221 .expect("the domain certificate query must return content");
4222 match response.content_type {
4223 Some(ContentType::CertificatesByAddress(list)) => list
4224 .certificates
4225 .iter()
4226 .map(|entry| entry.certificate_summaries.len())
4227 .sum::<usize>(),
4228 _ => panic!("the domain query must answer with CertificatesByAddress"),
4229 }
4230 };
4231
4232 for query in [
4233 "MiXeD.Example.COM",
4234 "mixed.example.com",
4235 "MIXED.EXAMPLE.COM",
4236 ] {
4237 assert_eq!(
4238 summaries_for(query),
4239 1,
4240 "querying {query:?} must find the certificate stored as MiXeD.Example.COM",
4241 );
4242 }
4243
4244 assert_eq!(
4245 summaries_for("other.example.com"),
4246 0,
4247 "normalising the query must not make an unrelated domain match",
4248 );
4249 }
4250
4251 fn https_mux_session_with_peer() -> (HttpsSession, std::net::TcpStream) {
4255 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4256 let listener = proxy
4257 .listeners
4258 .values()
4259 .next()
4260 .expect("the test proxy has one listener")
4261 .clone();
4262 let pool = Rc::downgrade(&proxy.pool);
4263 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4264 .expect("a test rustls server session must be created");
4265 let public_address = *listener.borrow().get_addr();
4266 let proxy = Rc::new(RefCell::new(proxy));
4267
4268 let accepting = std::net::TcpListener::bind("127.0.0.1:0")
4269 .expect("test listener must bind to a loopback port");
4270 let address = accepting
4271 .local_addr()
4272 .expect("test listener must report its local address");
4273 let stream = std::net::TcpStream::connect(address).expect("loopback connect must complete");
4274 let (client, client_address) = accepting.accept().expect("loopback accept must complete");
4275 client
4276 .set_nonblocking(true)
4277 .expect("the client stand-in must be nonblocking");
4278 stream
4279 .set_nonblocking(true)
4280 .expect("mio requires a nonblocking stream");
4281
4282 let mut session = HttpsSession::new(
4283 Duration::from_secs(30),
4284 Duration::from_secs(30),
4285 Duration::from_secs(30),
4286 Duration::from_secs(30),
4287 false,
4288 listener,
4289 pool,
4290 proxy,
4291 public_address,
4292 rustls_details,
4293 MioTcpStream::from_std(stream),
4294 client_address,
4295 Token(0),
4296 Duration::from_secs(0),
4297 );
4298 let handshake = match session.state.take() {
4299 HttpsStateMachine::Handshake(handshake) => handshake,
4300 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4301 };
4302 session.state = session
4303 .upgrade_handshake(handshake)
4304 .expect("the handshake must upgrade to a mux session");
4305 (session, client)
4306 }
4307
4308 fn frontend_shutdowns_of_a_close(
4312 mut session: HttpsSession,
4313 client: &mut std::net::TcpStream,
4314 ) -> (usize, bool) {
4315 use std::io::Read;
4316
4317 let before = mux::shutdown_writes();
4318 session.close();
4319 let shutdowns = mux::shutdown_writes() - before;
4320 drop(session);
4321
4322 let deadline = Instant::now() + Duration::from_secs(2);
4323 let mut chunk = [0u8; 4096];
4324 let saw_fin = loop {
4325 match client.read(&mut chunk) {
4326 Ok(0) => break true,
4327 Ok(_) => {}
4328 Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
4329 std::thread::sleep(Duration::from_millis(5));
4330 }
4331 Err(_) => break false,
4332 }
4333 };
4334 (shutdowns, saw_fin)
4335 }
4336
4337 #[test]
4353 fn https_close_skips_the_frontend_shutdown_once_the_client_closed() {
4354 for how in ["epoll HUP", "read EOF"] {
4355 let (mut session, mut client) = https_mux_session_with_peer();
4356 client
4357 .shutdown(std::net::Shutdown::Write)
4358 .expect("the client stand-in must send its FIN");
4359 let HttpsStateMachine::Mux(mux) = &mut session.state else {
4360 unreachable!("https_mux_session_with_peer returns a mux session")
4361 };
4362 if how == "epoll HUP" {
4363 mux.frontend.readiness_mut().event.insert(Ready::HUP);
4364 } else {
4365 match &mut mux.frontend {
4366 mux::Connection::H1(connection) => connection.socket.peer_disconnected = true,
4367 mux::Connection::H2(connection) => connection.socket.peer_disconnected = true,
4368 }
4369 }
4370
4371 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4372 assert_eq!(
4373 shutdowns, 0,
4374 "a frontend whose client closed ({how}) must not be shut down before its close"
4375 );
4376 assert!(
4377 saw_fin,
4378 "the close alone must still deliver the FIN to a half-closed client ({how})"
4379 );
4380 }
4381 }
4382
4383 #[test]
4386 fn https_close_keeps_the_frontend_shutdown_while_the_client_is_there() {
4387 let (session, mut client) = https_mux_session_with_peer();
4388 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4389 assert_eq!(
4390 shutdowns, 1,
4391 "a frontend whose client is still there must keep its shutdown(SHUT_WR)"
4392 );
4393 assert!(saw_fin, "the client must observe the FIN");
4394 }
4395}
4396
4397#[cfg(test)]
4398mod listener_sibling_tests {
4399 use sozu_command::{
4400 config::ListenerBuilder,
4401 proto::command::{
4402 CertificateAndKey, HstsConfig, PathRule, RequestHttpFrontend, SocketAddress,
4403 },
4404 };
4405
4406 use super::*;
4407 use crate::testing::{ServerParts, prebuild_server, provide_port};
4408
4409 fn proxy() -> HttpsProxy {
4410 let ServerParts {
4411 registry,
4412 sessions,
4413 pool,
4414 backends,
4415 ..
4416 } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4417 HttpsProxy::new(registry, sessions, pool, backends)
4418 }
4419
4420 fn add(proxy: &mut HttpsProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
4421 let config = ListenerBuilder::new_https(address)
4422 .with_interface(interface)
4423 .to_tls(None)
4424 .expect("could not build the listener config");
4425 proxy
4426 .add_listener(config, Token(token))
4427 .expect("could not add the listener");
4428 }
4429
4430 fn certificate(address: SocketAddress) -> AddCertificate {
4431 AddCertificate {
4432 address,
4433 certificate: CertificateAndKey {
4434 certificate: include_str!("../assets/certificate.pem").to_owned(),
4435 key: include_str!("../assets/key.pem").to_owned(),
4436 certificate_chain: Vec::new(),
4437 versions: Vec::new(),
4438 names: vec!["example.com".to_owned()],
4439 },
4440 expired_at: None,
4441 }
4442 }
4443
4444 fn front(address: SocketAddress) -> RequestHttpFrontend {
4445 RequestHttpFrontend {
4446 cluster_id: Some("cluster-a".to_owned()),
4447 address,
4448 hostname: "example.com".to_owned(),
4449 path: PathRule::prefix("/".to_owned()),
4450 ..Default::default()
4451 }
4452 }
4453
4454 fn serves_certificate(proxy: &HttpsProxy, token: usize) -> bool {
4455 proxy.listeners[&Token(token)]
4456 .borrow()
4457 .resolver
4458 .0
4459 .lock()
4460 .expect("resolver lock")
4461 .domains
4462 .domain_lookup(b"example.com", true)
4463 .is_some()
4464 }
4465
4466 fn routes(proxy: &HttpsProxy, token: usize) -> bool {
4467 proxy.listeners[&Token(token)]
4468 .borrow()
4469 .fronts
4470 .lookup("example.com", "/", &Method::Get)
4471 .is_ok()
4472 }
4473
4474 #[test]
4480 fn a_listener_added_next_to_a_sibling_takes_over_its_certificates_and_frontends() {
4481 let mut proxy = proxy();
4482 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4483 add(&mut proxy, address, None, 0);
4484 proxy
4485 .add_certificate(certificate(address))
4486 .expect("add the certificate");
4487 proxy
4488 .add_https_frontend(front(address))
4489 .expect("add the frontend");
4490 add(&mut proxy, address, Some("lo"), 1);
4491
4492 assert!(serves_certificate(&proxy, 0));
4493 assert!(
4494 serves_certificate(&proxy, 1),
4495 "the listener added after the certificate must serve it too"
4496 );
4497 assert!(
4498 routes(&proxy, 1),
4499 "the listener added after the frontend must route it too"
4500 );
4501
4502 proxy
4503 .remove_https_frontend(front(address))
4504 .expect("remove the frontend");
4505 assert!(!routes(&proxy, 0));
4506 assert!(!routes(&proxy, 1));
4507 }
4508 fn hsts(max_age: u32) -> HstsConfig {
4509 HstsConfig {
4510 enabled: Some(true),
4511 max_age: Some(max_age),
4512 ..Default::default()
4513 }
4514 }
4515
4516 fn add_with_hsts(
4517 proxy: &mut HttpsProxy,
4518 address: SocketAddress,
4519 interface: Option<&str>,
4520 token: usize,
4521 hsts: Option<HstsConfig>,
4522 ) {
4523 let mut config = ListenerBuilder::new_https(address)
4524 .with_interface(interface)
4525 .to_tls(None)
4526 .expect("could not build the listener config");
4527 config.hsts = hsts;
4528 proxy
4529 .add_listener(config, Token(token))
4530 .expect("could not add the listener");
4531 }
4532
4533 fn tagged_front(address: SocketAddress) -> RequestHttpFrontend {
4536 RequestHttpFrontend {
4537 tags: BTreeMap::from([("owner".to_owned(), "team-a".to_owned())]),
4538 ..front(address)
4539 }
4540 }
4541
4542 fn served_hsts(proxy: &HttpsProxy, token: usize) -> Option<String> {
4545 let route = proxy.listeners[&Token(token)]
4546 .borrow()
4547 .fronts
4548 .lookup("example.com", "/", &Method::Get)
4549 .expect("the listener must route example.com");
4550 route
4551 .headers_response
4552 .iter()
4553 .find(|edit| edit.key.eq_ignore_ascii_case(b"strict-transport-security"))
4554 .map(|edit| String::from_utf8_lossy(&edit.val).into_owned())
4555 }
4556
4557 fn patch_hsts(proxy: &HttpsProxy, token: usize, address: SocketAddress, hsts: HstsConfig) {
4558 proxy.listeners[&Token(token)]
4559 .borrow_mut()
4560 .update_config(&UpdateHttpsListenerConfig {
4561 address,
4562 hsts: Some(hsts),
4563 ..Default::default()
4564 })
4565 .expect("patch the listener HSTS default");
4566 }
4567
4568 #[test]
4581 fn a_rejected_patch_leaves_the_listener_unchanged() {
4582 use sozu_command::proto::command::{AlpnProtocols, UpdateHttpsListenerConfig};
4583
4584 let mut proxy = proxy();
4585 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4586 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4587 proxy
4588 .add_https_frontend(tagged_front(address))
4589 .expect("add the frontend");
4590
4591 let valid_prefix = UpdateHttpsListenerConfig {
4592 address,
4593 front_timeout: Some(99),
4594 sozu_id_header: Some("X-Edge-Id".to_owned()),
4595 alpn_protocols: Some(AlpnProtocols {
4596 values: vec!["http/1.1".to_owned()],
4597 }),
4598 answers: BTreeMap::from([(
4599 "404".to_owned(),
4600 "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_owned(),
4601 )]),
4602 ..Default::default()
4603 };
4604 let rejected = [
4605 UpdateHttpsListenerConfig {
4606 hsts: Some(HstsConfig {
4607 enabled: None,
4608 max_age: Some(200),
4609 ..Default::default()
4610 }),
4611 ..valid_prefix.clone()
4612 },
4613 UpdateHttpsListenerConfig {
4614 answers: BTreeMap::from([(
4615 "503".to_owned(),
4616 "not a valid http response".to_owned(),
4617 )]),
4618 hsts: Some(hsts(200)),
4619 ..valid_prefix.clone()
4620 },
4621 ];
4622 for patch in rejected {
4623 let listener = &proxy.listeners[&Token(0)];
4624 let config_before = listener.borrow().config.clone();
4625 let rustls_before = listener.borrow().rustls_details.clone();
4626 let answers_before = listener.borrow().get_answers().clone();
4627 listener
4628 .borrow_mut()
4629 .update_config(&patch)
4630 .expect_err("the patch must be refused");
4631 let listener = listener.borrow();
4632 assert_eq!(
4633 listener.config, config_before,
4634 "a refused patch must leave the listener configuration unchanged"
4635 );
4636 assert!(
4637 Arc::ptr_eq(&rustls_before, &listener.rustls_details),
4638 "a refused patch must not publish a new rustls context"
4639 );
4640 assert!(
4641 Rc::ptr_eq(&answers_before, listener.get_answers()),
4642 "a refused patch must not publish a new answer registry"
4643 );
4644 drop(listener);
4645 assert_eq!(
4646 served_hsts(&proxy, 0).as_deref(),
4647 Some("max-age=100"),
4648 "a refused patch must not refresh the frontends' HSTS"
4649 );
4650 }
4651
4652 proxy.listeners[&Token(0)]
4653 .borrow_mut()
4654 .update_config(&valid_prefix)
4655 .expect("the valid prefix alone must apply");
4656 let listener = proxy.listeners[&Token(0)].borrow();
4657 assert_eq!(listener.config.front_timeout, 99);
4658 assert_eq!(listener.config.alpn_protocols, vec!["http/1.1".to_owned()]);
4659 }
4660
4661 #[test]
4666 fn a_copied_frontend_without_hsts_inherits_the_new_listener_default() {
4667 let mut proxy = proxy();
4668 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4669 add_with_hsts(&mut proxy, address, None, 0, None);
4670 proxy
4671 .add_https_frontend(tagged_front(address))
4672 .expect("add the frontend");
4673 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(100)));
4674
4675 assert_eq!(served_hsts(&proxy, 0), None);
4676 assert_eq!(
4677 served_hsts(&proxy, 1).as_deref(),
4678 Some("max-age=100"),
4679 "the listener added after the frontend must serve its own HSTS default"
4680 );
4681
4682 patch_hsts(&proxy, 0, address, hsts(200));
4684 patch_hsts(&proxy, 1, address, hsts(300));
4685 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=200"));
4686 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=300"));
4687 }
4688
4689 #[test]
4692 fn a_frontend_with_explicit_hsts_is_never_overridden_by_a_listener_default() {
4693 let mut proxy = proxy();
4694 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4695 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4696 proxy
4697 .add_https_frontend(RequestHttpFrontend {
4698 hsts: Some(hsts(42)),
4699 ..tagged_front(address)
4700 })
4701 .expect("add the frontend");
4702 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(300)));
4703
4704 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4705 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4706
4707 patch_hsts(&proxy, 0, address, hsts(500));
4708 patch_hsts(&proxy, 1, address, hsts(600));
4709 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4710 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4711 }
4712
4713 #[test]
4717 fn a_listener_hsts_patch_refreshes_every_frontend_without_its_own_hsts() {
4718 for initial in [None, Some(hsts(100))] {
4719 let mut proxy = proxy();
4720 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4721 add_with_hsts(&mut proxy, address, None, 0, initial);
4722 proxy
4723 .add_https_frontend(tagged_front(address))
4724 .expect("add the frontend");
4725
4726 patch_hsts(&proxy, 0, address, hsts(700));
4727 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=700"));
4728
4729 patch_hsts(
4730 &proxy,
4731 0,
4732 address,
4733 HstsConfig {
4734 enabled: Some(false),
4735 ..Default::default()
4736 },
4737 );
4738 assert_eq!(served_hsts(&proxy, 0), None);
4739 }
4740 }
4741
4742 const TEST_CA_PEM: &str = include_str!("../assets/certificate.pem");
4744
4745 fn test_crypto_provider() -> Arc<CryptoProvider> {
4746 Arc::new(default_provider())
4747 }
4748
4749 fn https_config_with_client_auth(
4750 mode: ClientAuthMode,
4751 cas: &[&str],
4752 crls: &[&str],
4753 ) -> HttpsListenerConfig {
4754 let address = SocketAddress::new_v4(127, 0, 0, 1, 1049);
4755 let mut config = ListenerBuilder::new_https(address)
4756 .to_tls(None)
4757 .expect("default HTTPS listener config");
4758 config.client_auth = Some(mode as i32);
4759 config.client_ca_certificates = cas.iter().map(|s| s.to_string()).collect();
4760 config.client_ca_crls = crls.iter().map(|s| s.to_string()).collect();
4761 config
4762 }
4763
4764 #[test]
4765 fn client_cert_verifier_none_is_disabled() {
4766 let provider = test_crypto_provider();
4768 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthNone, &[], &[]);
4769 assert!(
4770 HttpsListener::client_cert_verifier(&config, &provider)
4771 .expect("verifier build")
4772 .is_none()
4773 );
4774
4775 let mut absent = config;
4776 absent.client_auth = None;
4777 assert!(
4778 HttpsListener::client_cert_verifier(&absent, &provider)
4779 .expect("verifier build")
4780 .is_none(),
4781 "absent client_auth must decode to NONE"
4782 );
4783 }
4784
4785 #[test]
4786 fn client_cert_verifier_rejects_unknown_mode() {
4787 let provider = test_crypto_provider();
4790 let mut config =
4791 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4792 config.client_auth = Some(999);
4793 assert!(matches!(
4794 HttpsListener::client_cert_verifier(&config, &provider),
4795 Err(ListenerError::ClientAuth(_))
4796 ));
4797 }
4798
4799 #[test]
4800 fn client_cert_verifier_required_builds_with_ca() {
4801 let provider = test_crypto_provider();
4802 let config =
4803 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4804 assert!(
4805 HttpsListener::client_cert_verifier(&config, &provider)
4806 .expect("verifier build")
4807 .is_some()
4808 );
4809 }
4810
4811 #[test]
4812 fn client_cert_verifier_optional_builds_with_ca() {
4813 let provider = test_crypto_provider();
4814 let config =
4815 https_config_with_client_auth(ClientAuthMode::ClientAuthOptional, &[TEST_CA_PEM], &[]);
4816 assert!(
4817 HttpsListener::client_cert_verifier(&config, &provider)
4818 .expect("verifier build")
4819 .is_some()
4820 );
4821 }
4822
4823 #[test]
4824 fn client_cert_verifier_requires_a_trusted_ca() {
4825 let provider = test_crypto_provider();
4828 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[], &[]);
4829 assert!(matches!(
4830 HttpsListener::client_cert_verifier(&config, &provider),
4831 Err(ListenerError::ClientAuth(_))
4832 ));
4833 }
4834
4835 #[test]
4836 fn client_cert_verifier_rejects_malformed_ca() {
4837 let provider = test_crypto_provider();
4838 let config = https_config_with_client_auth(
4839 ClientAuthMode::ClientAuthRequired,
4840 &["not a pem certificate"],
4841 &[],
4842 );
4843 assert!(matches!(
4845 HttpsListener::client_cert_verifier(&config, &provider),
4846 Err(ListenerError::ClientAuth(_))
4847 ));
4848 }
4849
4850 #[test]
4851 fn client_cert_verifier_rejects_empty_ca_entry_among_valid_ones() {
4852 let provider = test_crypto_provider();
4856 let config = https_config_with_client_auth(
4857 ClientAuthMode::ClientAuthRequired,
4858 &[TEST_CA_PEM, "not a certificate"],
4859 &[],
4860 );
4861 assert!(matches!(
4862 HttpsListener::client_cert_verifier(&config, &provider),
4863 Err(ListenerError::ClientAuth(_))
4864 ));
4865 }
4866
4867 #[test]
4868 fn client_cert_verifier_rejects_empty_crl_entry() {
4869 let provider = test_crypto_provider();
4872 let config = https_config_with_client_auth(
4873 ClientAuthMode::ClientAuthRequired,
4874 &[TEST_CA_PEM],
4875 &["not a crl"],
4876 );
4877 assert!(matches!(
4878 HttpsListener::client_cert_verifier(&config, &provider),
4879 Err(ListenerError::ClientAuth(_))
4880 ));
4881 }
4882
4883 const MTLS_CA_PEM: &str = include_str!("../assets/mtls/ca-cert.pem");
4892 const MTLS_CLIENT_PEM: &str = include_str!("../assets/mtls/client-cert.pem");
4893 const MTLS_CRL_CURRENT: &str = include_str!("../assets/mtls/crl-current.pem");
4895 const MTLS_CRL_REVOKED: &str = include_str!("../assets/mtls/crl-revoked.pem");
4897 const MTLS_CRL_EXPIRED: &str = include_str!("../assets/mtls/crl-expired.pem");
4899 const MTLS_CRL_OTHER_CA: &str = include_str!("../assets/mtls/crl-other-ca.pem");
4901 const MTLS_CRL_PARTITION_A: &str = include_str!("../assets/mtls/crl-partition-a.pem");
4904 const MTLS_CRL_PARTITION_B: &str = include_str!("../assets/mtls/crl-partition-b.pem");
4905
4906 fn verify_mtls_client(crls: &[&str]) -> Result<(), rustls::Error> {
4909 verify_mtls_client_at(crls, rustls::pki_types::UnixTime::now())
4910 }
4911
4912 fn verify_mtls_client_at(
4914 crls: &[&str],
4915 time: rustls::pki_types::UnixTime,
4916 ) -> Result<(), rustls::Error> {
4917 let config =
4918 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4919 let verifier = HttpsListener::client_cert_verifier(&config, &test_crypto_provider())
4920 .expect("a CA and well-formed CRLs must build a verifier")
4921 .expect("required client auth must install a verifier");
4922 let client = CertificateDer::from_pem_slice(MTLS_CLIENT_PEM.as_bytes())
4923 .expect("the client fixture is a PEM certificate");
4924 verifier.verify_client_cert(&client, &[], time).map(|_| ())
4925 }
4926
4927 fn build_mtls_listener(crls: &[&str]) -> Result<(), String> {
4930 let config =
4931 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4932 match HttpsListener::client_cert_verifier(&config, &test_crypto_provider()) {
4933 Ok(_) => Ok(()),
4934 Err(ListenerError::ClientAuth(reason)) => Err(reason),
4935 Err(other) => panic!("unexpected listener error {other:?}"),
4936 }
4937 }
4938
4939 #[test]
4940 fn client_revocation_accepts_a_client_no_crl_revokes() {
4941 assert_eq!(
4942 verify_mtls_client(&[]),
4943 Ok(()),
4944 "precondition: the client chains to the CA"
4945 );
4946 assert_eq!(
4947 verify_mtls_client(&[MTLS_CRL_CURRENT]),
4948 Ok(()),
4949 "a current CRL from the client's issuer that does not list it must not reject it"
4950 );
4951 }
4952
4953 #[test]
4954 fn client_revocation_rejects_a_revoked_client() {
4955 assert_eq!(
4958 verify_mtls_client(&[MTLS_CRL_REVOKED]),
4959 Err(rustls::Error::InvalidCertificate(
4960 rustls::CertificateError::Revoked
4961 ))
4962 );
4963 }
4964
4965 #[test]
4966 fn client_revocation_refuses_an_expired_crl_only_where_the_operator_states_it() {
4967 let now = i64::try_from(
4970 std::time::SystemTime::now()
4971 .duration_since(std::time::UNIX_EPOCH)
4972 .expect("a clock after 1970")
4973 .as_secs(),
4974 )
4975 .expect("a clock within i64");
4976 let refused = HttpsListener::check_crls_current(&[MTLS_CRL_EXPIRED.to_owned()], now);
4977 assert!(
4978 matches!(refused, Err(ListenerError::ClientAuth(ref reason)) if reason.contains("expired")),
4979 "an expired CRL must be refused when stated, got {refused:?}"
4980 );
4981 assert!(
4982 HttpsListener::check_crls_current(
4983 &[MTLS_CRL_CURRENT.to_owned(), MTLS_CRL_OTHER_CA.to_owned()],
4984 now
4985 )
4986 .is_ok()
4987 );
4988 assert_eq!(build_mtls_listener(&[MTLS_CRL_EXPIRED]), Ok(()));
4992 let config = https_config_with_client_auth(
4993 ClientAuthMode::ClientAuthRequired,
4994 &[MTLS_CA_PEM],
4995 &[MTLS_CRL_EXPIRED],
4996 );
4997 assert!(HttpsListener::try_new(config, Token(0)).is_ok());
4998 }
4999
5000 #[test]
5001 fn client_revocation_rejects_a_client_once_its_crl_has_lapsed() {
5002 let june_2125 =
5009 rustls::pki_types::UnixTime::since_unix_epoch(Duration::from_secs(4_905_100_800));
5010 let verdict = verify_mtls_client_at(&[MTLS_CRL_CURRENT], june_2125);
5011 assert!(
5012 matches!(
5013 verdict,
5014 Err(rustls::Error::InvalidCertificate(
5015 rustls::CertificateError::ExpiredRevocationList
5016 | rustls::CertificateError::ExpiredRevocationListContext { .. }
5017 ))
5018 ),
5019 "a CRL past its nextUpdate must reject the client, got {verdict:?}"
5020 );
5021 assert_eq!(verify_mtls_client_at(&[], june_2125), Ok(()));
5024 }
5025
5026 #[test]
5027 fn client_revocation_rejects_a_client_no_configured_crl_covers() {
5028 assert_eq!(
5032 verify_mtls_client(&[MTLS_CRL_OTHER_CA]),
5033 Err(rustls::Error::InvalidCertificate(
5034 rustls::CertificateError::UnknownRevocationStatus
5035 ))
5036 );
5037 assert_eq!(
5039 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_CURRENT]),
5040 Ok(())
5041 );
5042 }
5043
5044 #[test]
5045 fn client_revocation_refuses_two_crls_of_one_issuer_with_overlapping_scopes() {
5046 for crls in [
5050 vec![MTLS_CRL_CURRENT, MTLS_CRL_REVOKED],
5051 vec![MTLS_CRL_REVOKED, MTLS_CRL_CURRENT],
5052 vec![MTLS_CRL_CURRENT, MTLS_CRL_PARTITION_A],
5054 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_CURRENT],
5055 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_A],
5057 ] {
5058 let refused = build_mtls_listener(&crls);
5059 assert!(
5060 refused
5061 .as_ref()
5062 .is_err_and(|reason| reason.contains("overlapping")),
5063 "overlapping CRLs of one issuer must fail the listener, got {refused:?}"
5064 );
5065 }
5066 assert!(build_mtls_listener(&[&format!("{MTLS_CRL_CURRENT}{MTLS_CRL_REVOKED}")]).is_err());
5068 }
5069
5070 #[test]
5071 fn client_revocation_takes_the_partitions_of_one_issuer() {
5072 assert_eq!(
5075 build_mtls_listener(&[MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_B]),
5076 Ok(())
5077 );
5078 }
5079
5080 #[test]
5081 fn client_revocation_takes_one_crl_per_issuer() {
5082 assert_eq!(
5085 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_REVOKED]),
5086 Err(rustls::Error::InvalidCertificate(
5087 rustls::CertificateError::Revoked
5088 ))
5089 );
5090 assert_eq!(
5091 verify_mtls_client(&[MTLS_CRL_REVOKED, MTLS_CRL_OTHER_CA]),
5092 Err(rustls::Error::InvalidCertificate(
5093 rustls::CertificateError::Revoked
5094 ))
5095 );
5096 }
5097
5098 #[test]
5104 fn a_client_auth_policy_patch_rebuilds_the_listener_verifier() {
5105 let config = https_config_with_client_auth(
5106 ClientAuthMode::ClientAuthRequired,
5107 &[MTLS_CA_PEM],
5108 &[MTLS_CRL_CURRENT],
5109 );
5110 let mut listener =
5111 HttpsListener::try_new(config.clone(), Token(0)).expect("build the mTLS listener");
5112 let original_context = listener.rustls_details.clone();
5113
5114 let refreshed = HttpsListenerConfig {
5115 client_ca_crls: vec![MTLS_CRL_REVOKED.to_owned()],
5116 ..config.clone()
5117 }
5118 .client_auth_policy();
5119 listener
5120 .update_config(&UpdateHttpsListenerConfig {
5121 address: config.address,
5122 client_auth_policy: Some(refreshed.clone()),
5123 ..Default::default()
5124 })
5125 .expect("a buildable policy must be applied");
5126 assert_eq!(listener.config.client_auth_policy(), refreshed);
5127 assert!(
5128 !Arc::ptr_eq(&listener.rustls_details, &original_context),
5129 "the verifier lives in the rustls context, which must be rebuilt"
5130 );
5131
5132 let applied_context = listener.rustls_details.clone();
5133 let unbuildable = ClientAuthPolicy {
5134 client_auth: Some(ClientAuthMode::ClientAuthRequired as i32),
5135 client_ca_certificates: vec![],
5136 client_ca_crls: vec![],
5137 };
5138 assert!(
5139 listener
5140 .update_config(&UpdateHttpsListenerConfig {
5141 address: config.address,
5142 client_auth_policy: Some(unbuildable.clone()),
5143 ..Default::default()
5144 })
5145 .is_err(),
5146 "required client auth with no trusted CA must be refused"
5147 );
5148 assert!(HttpsListener::validate_client_auth_policy(&unbuildable).is_err());
5149 assert_eq!(
5150 listener.config.client_auth_policy(),
5151 refreshed,
5152 "a refused policy leaves the applied one in place"
5153 );
5154 assert!(Arc::ptr_eq(&listener.rustls_details, &applied_context));
5155 }
5156}