1use std::{
13 cell::RefCell,
14 collections::{BTreeMap, HashMap, hash_map::Entry},
15 io::ErrorKind,
16 net::SocketAddr as StdSocketAddr,
17 rc::{Rc, Weak},
18 str::{from_utf8, from_utf8_unchecked},
19 sync::Arc,
20 time::{Duration, Instant},
21};
22
23use mio::{
24 Interest, Registry, Token,
25 net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream},
26};
27use rustls::{
28 CipherSuite, ProtocolVersion, RootCertStore, ServerConfig as RustlsServerConfig,
29 ServerConnection, SupportedCipherSuite,
30 crypto::CryptoProvider,
31 pki_types::{CertificateDer, CertificateRevocationListDer, pem::PemObject},
32 server::WebPkiClientVerifier,
33};
34use rusty_ulid::Ulid;
35use socket2::SockRef;
36use sozu_command::{
37 certificate::Fingerprint,
38 config::{DEFAULT_ALPN_PROTOCOLS, DEFAULT_CIPHER_LIST},
39 listener_key::ListenerKey,
40 proto::command::{
41 AddCertificate, CertificateSummary, CertificatesByAddress, ClientAuthMode,
42 ClientAuthPolicy, Cluster, ForwardedHeaders, HttpsListenerConfig,
43 ListOfCertificatesByAddress, ListenerType, RemoveCertificate, RemoveListener,
44 ReplaceCertificate, RequestHttpFrontend, ResponseContent, TlsVersion,
45 UpdateHttpsListenerConfig, WorkerRequest, WorkerResponse, request::RequestType,
46 response_content::ContentType,
47 },
48 ready::Ready,
49 response::HttpFrontend,
50 state::{
51 ClusterId, validate_alpn_protocols, validate_forwarded_headers,
52 validate_h2_flood_knobs_https, validate_sozu_id_header,
53 },
54};
55
56use crate::metrics::names;
57use crate::{
58 AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError,
59 ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed,
60 SessionMetrics, SessionResult, StateMachineBuilder, StateResult,
61 backends::BackendMap,
62 crypto::{cipher_suite_by_name, default_provider, kx_group_by_name},
63 pool::Pool,
64 protocol::{
65 Pipe, SessionState,
66 http::answers::HttpAnswers,
67 http::parser::{Method, hostname_and_port},
68 mux::{self, Mux, MuxTls},
69 proxy_protocol::expect::ExpectProxyProtocol,
70 rustls::TlsHandshake,
71 },
72 router::{RequestHeaders, RouteResult, Router},
73 server::{ListenToken, SessionManager},
74 socket::{FrontRustls, RecvMemory, server_bind},
75 timer::TimeoutContainer,
76 tls::MutexCertificateResolver,
77};
78
79StateMachineBuilder! {
80 enum HttpsStateMachine impl SessionState {
87 Expect(ExpectProxyProtocol<MioTcpStream>, ServerConnection),
88 Handshake(TlsHandshake),
89 Mux(MuxTls),
90 WebSocket(Pipe<FrontRustls, HttpsListener>),
91 }
92}
93
94enum AlpnProtocol {
95 H2,
96 Http11,
97}
98
99#[cfg(debug_assertions)]
104fn https_stage_rank(marker: StateMarker) -> u8 {
105 match marker {
106 StateMarker::Expect => 0,
107 StateMarker::Handshake => 1,
108 StateMarker::Mux => 2,
109 StateMarker::WebSocket => 3,
110 }
111}
112
113macro_rules! log_module_context {
120 () => {{
121 let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
122 format!("{open}HTTPS{reset}\t >>>", open = open, reset = reset)
123 }};
124}
125
126macro_rules! log_context {
132 ($self:expr) => {{
133 let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
134 format!(
135 "{open}HTTPS{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}peer{reset}={white}{peer}{reset})\t >>>",
136 open = open,
137 reset = reset,
138 grey = grey,
139 gray = gray,
140 white = white,
141 frontend = $self.frontend_token.0,
142 peer = $self.peer_address.map(|a| a.to_string()).unwrap_or_else(|| "<none>".to_string()),
143 )
144 }};
145}
146
147fn successful_tls_handshake_summary(sni: Option<&str>, alpn: Option<&str>) -> String {
148 format!(
149 "successful TLS handshake (sni_bytes={:?}, alpn_bytes={:?})",
150 sni.map(str::len),
151 alpn.map(str::len),
152 )
153}
154
155fn upgraded_frontend_events(handshake_event: Ready, rustls_holds_input: bool) -> Ready {
186 let hup = handshake_event & (Ready::HUP | Ready::WRITE_CLOSED);
188 if handshake_event.is_readable() || rustls_holds_input {
189 Ready::READABLE | Ready::WRITABLE | hup
190 } else {
191 Ready::WRITABLE | hup
192 }
193}
194
195fn certificate_summary_domain(key: &[u8]) -> String {
209 match from_utf8(key) {
210 Ok(domain) => domain.to_owned(),
211 Err(error) => {
212 error!(
213 "{} certificate domain is not valid UTF-8; the reported name is lossy and will not \
214 match a later RemoveCertificate, domain_bytes={} valid_up_to={}",
215 log_module_context!(),
216 key.len(),
217 error.valid_up_to(),
218 );
219 String::from_utf8_lossy(key).into_owned()
220 }
221 }
222}
223
224pub struct HttpsSession {
225 configured_backend_timeout: Duration,
226 configured_connect_timeout: Duration,
227 configured_frontend_timeout: Duration,
228 frontend_token: Token,
229 has_been_closed: bool,
230 last_event: Instant,
231 listener: Rc<RefCell<HttpsListener>>,
232 metrics: SessionMetrics,
233 peer_address: Option<StdSocketAddr>,
234 pool: Weak<RefCell<Pool>>,
235 proxy: Rc<RefCell<HttpsProxy>>,
236 public_address: StdSocketAddr,
237 state: HttpsStateMachine,
238}
239
240impl HttpsSession {
241 #[allow(clippy::too_many_arguments)]
242 pub fn new(
243 configured_backend_timeout: Duration,
244 configured_connect_timeout: Duration,
245 configured_frontend_timeout: Duration,
246 configured_request_timeout: Duration,
247 expect_proxy: bool,
248 listener: Rc<RefCell<HttpsListener>>,
249 pool: Weak<RefCell<Pool>>,
250 proxy: Rc<RefCell<HttpsProxy>>,
251 public_address: StdSocketAddr,
252 rustls_details: ServerConnection,
253 sock: MioTcpStream,
254 peer: StdSocketAddr,
255 token: Token,
256 wait_time: Duration,
257 ) -> HttpsSession {
258 debug_assert!(
263 !configured_request_timeout.is_zero(),
264 "HTTPS session request timeout must be non-zero (would arm an immediate deadline)"
265 );
266 debug_assert!(
267 !configured_frontend_timeout.is_zero() && !configured_backend_timeout.is_zero(),
268 "HTTPS session front/back timeouts must be non-zero"
269 );
270
271 let peer_address = if expect_proxy {
272 None
274 } else {
275 Some(peer)
277 };
278
279 let request_id = Ulid::generate();
280 let container_frontend_timeout = TimeoutContainer::new(configured_request_timeout, token);
281
282 let state = if expect_proxy {
283 trace!("{} starting in expect proxy state", log_module_context!());
284 gauge_add!(names::protocol::PROXY_EXPECT, 1);
285 HttpsStateMachine::Expect(
286 ExpectProxyProtocol::new(container_frontend_timeout, sock, token, request_id),
287 rustls_details,
288 )
289 } else {
290 gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
291 HttpsStateMachine::Handshake(TlsHandshake::new(
292 container_frontend_timeout,
293 rustls_details,
294 sock,
295 token,
296 request_id,
297 peer_address,
298 ))
299 };
300
301 debug_assert_eq!(
306 matches!(state, HttpsStateMachine::Expect(..)),
307 expect_proxy,
308 "fresh HTTPS session must start in Expect iff expect_proxy is set"
309 );
310 debug_assert!(
311 expect_proxy || matches!(state, HttpsStateMachine::Handshake(_)),
312 "non-expect-proxy HTTPS session must start in the TLS Handshake state"
313 );
314 debug_assert!(
315 !expect_proxy || peer_address.is_none(),
316 "expect-proxy peer address is only known after the PROXY header is parsed"
317 );
318
319 let metrics = SessionMetrics::new(Some(wait_time));
320 HttpsSession {
321 configured_backend_timeout,
322 configured_connect_timeout,
323 configured_frontend_timeout,
324 frontend_token: token,
325 has_been_closed: false,
326 last_event: Instant::now(),
327 listener,
328 metrics,
329 peer_address,
330 pool,
331 proxy,
332 public_address,
333 state,
334 }
335 }
336
337 pub fn upgrade(&mut self) -> SessionIsToBeClosed {
338 debug!("{} upgrade", log_context!(self));
339 #[cfg(debug_assertions)]
345 let from_marker = self.state.marker();
346 let new_state = match self.state.take() {
347 HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl),
348 HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake),
349 HttpsStateMachine::Mux(mux) => self.upgrade_mux(mux),
350 HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss),
351 HttpsStateMachine::FailedUpgrade(_) => {
352 error!(
356 "{} upgrade called on FailedUpgrade state; closing session",
357 log_context!(self)
358 );
359 None
360 }
361 };
362
363 match new_state {
364 Some(state) => {
365 #[cfg(debug_assertions)]
375 debug_assert!(
376 https_stage_rank(state.marker()) > https_stage_rank(from_marker)
377 || matches!(from_marker, StateMarker::WebSocket),
378 "HTTPS upgrade must advance the lifecycle (from {:?} to {:?})",
379 from_marker,
380 state.marker()
381 );
382 debug_assert!(
383 !state.failed(),
384 "a successful HTTPS upgrade must not yield a FailedUpgrade state"
385 );
386 self.state = state;
387 false
388 }
389 None => {
391 debug_assert!(
395 self.state.failed(),
396 "a refused HTTPS upgrade must leave the state in FailedUpgrade"
397 );
398 #[cfg(debug_assertions)]
400 debug_assert!(
401 https_stage_rank(self.state.marker()) == https_stage_rank(from_marker),
402 "FailedUpgrade must retain the origin stage marker for gauge restoration"
403 );
404 true
405 }
406 }
407 }
408
409 fn upgrade_expect(
410 &mut self,
411 mut expect: ExpectProxyProtocol<MioTcpStream>,
412 ssl: ServerConnection,
413 ) -> Option<HttpsStateMachine> {
414 if let Some(ref addresses) = expect.addresses
415 && let (Some(public_address), Some(session_address)) =
416 (addresses.destination(), addresses.source())
417 {
418 self.public_address = public_address;
419 self.peer_address = Some(session_address);
420
421 let ExpectProxyProtocol {
422 container_frontend_timeout,
423 frontend,
424 frontend_readiness: readiness,
425 request_id,
426 ..
427 } = expect;
428
429 let mut handshake = TlsHandshake::new(
430 container_frontend_timeout,
431 ssl,
432 frontend,
433 self.frontend_token,
434 request_id,
435 self.peer_address,
436 );
437 handshake.frontend_readiness = readiness;
440 handshake.frontend_readiness.event.insert(Ready::READABLE);
441
442 debug_assert_eq!(
446 self.peer_address,
447 Some(session_address),
448 "expect upgrade must adopt the PROXY-advertised source as the peer address"
449 );
450 debug_assert!(
451 handshake.frontend_readiness.event.is_readable(),
452 "handshake handed off from expect must be armed for READABLE"
453 );
454
455 gauge_add!(names::protocol::PROXY_EXPECT, -1);
456 gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
457 return Some(HttpsStateMachine::Handshake(handshake));
458 }
459
460 if !expect.container_frontend_timeout.cancel() {
462 error!(
463 "{} failed to cancel request timeout on expect upgrade phase for 'expect proxy protocol with AF_UNSPEC address'",
464 log_context!(self)
465 );
466 }
467
468 None
469 }
470
471 fn upgrade_handshake(&mut self, handshake: TlsHandshake) -> Option<HttpsStateMachine> {
472 let sni_owned: Option<String> = handshake
484 .session
485 .server_name()
486 .map(|s| s.to_ascii_lowercase())
487 .map(|mut s| {
488 if s.ends_with('.') {
489 s.pop();
490 }
491 s
492 });
493 let alpn = handshake.session.alpn_protocol();
494 let alpn = alpn.and_then(|alpn| from_utf8(alpn).ok());
495 debug!(
496 "{} {}",
497 log_context!(self),
498 successful_tls_handshake_summary(sni_owned.as_deref(), alpn)
499 );
500
501 let disable_http11 = self.listener.borrow().is_http11_disabled();
506 let (alpn, alpn_label): (AlpnProtocol, Option<&'static str>) = match alpn {
511 Some("http/1.1") => {
512 if disable_http11 {
513 incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
514 warn!(
515 "{} rejecting TLS connection: listener is H2-only but client negotiated http/1.1",
516 log_context!(self)
517 );
518 return None;
519 }
520 (AlpnProtocol::Http11, Some("http/1.1"))
521 }
522 Some("h2") => (AlpnProtocol::H2, Some("h2")),
523 Some(other) => {
524 incr!(names::https::ALPN_REJECTED_UNSUPPORTED);
531 error!(
532 "{} unsupported ALPN protocol: alpn_bytes={}",
533 log_context!(self),
534 other.len()
535 );
536 return None;
537 }
538 None => {
542 if disable_http11 {
543 incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
544 warn!(
545 "{} rejecting TLS connection: listener is H2-only but client did not negotiate ALPN",
546 log_context!(self)
547 );
548 return None;
549 }
550 (AlpnProtocol::Http11, None)
551 }
552 };
553
554 debug_assert!(
560 !disable_http11 || matches!(alpn, AlpnProtocol::H2),
561 "H2-only listener must not dispatch an HTTP/1.1 session past ALPN"
562 );
563 debug_assert!(
564 match (&alpn, alpn_label) {
565 (AlpnProtocol::H2, Some(l)) => l == "h2",
566 (AlpnProtocol::Http11, Some(l)) => l == "http/1.1",
567 (AlpnProtocol::Http11, None) => true,
569 (AlpnProtocol::H2, None) => false,
570 },
571 "negotiated ALPN protocol and its wire label must agree"
572 );
573
574 let tls_version_label = handshake
579 .session
580 .protocol_version()
581 .and_then(rustls_version_label);
582 let tls_cipher_label = handshake
583 .session
584 .negotiated_cipher_suite()
585 .and_then(rustls_ciphersuite_label);
586 if let Some(version) = handshake.session.protocol_version() {
587 incr!(rustls_version_str(version));
588 };
589 if let Some(cipher) = handshake.session.negotiated_cipher_suite() {
590 incr!(rustls_ciphersuite_str(cipher));
591 };
592
593 gauge_add!(names::protocol::TLS_HANDSHAKE, -1);
594
595 let frontend_events = upgraded_frontend_events(
597 handshake.frontend_readiness.event,
598 !handshake.session.wants_read(),
599 );
600 let recv_memory =
604 RecvMemory::after_handshake(!handshake.frontend_readiness.event.is_readable());
605
606 let session_ulid = rusty_ulid::Ulid::generate();
607 let front_stream = FrontRustls {
608 stream: handshake.stream,
609 session: handshake.session,
610 peer_disconnected: false,
611 peer_reset: false,
612 tls_fatal: false,
613 session_ulid,
614 configured_peer: self.peer_address,
622 recv_memory,
623 };
624 let router = mux::Router::new(
625 self.configured_backend_timeout,
626 self.configured_connect_timeout,
627 );
628 let mut context = mux::Context::new(
629 session_ulid,
630 self.pool.clone(),
631 self.listener.clone(),
632 self.peer_address,
633 self.public_address,
634 );
635 let tls_cert_names: Option<Arc<Vec<String>>> = match sni_owned.as_deref() {
690 Some(sni) => self
691 .listener
692 .borrow()
693 .resolver()
694 .names_for_sni(sni.as_bytes())
695 .and_then(|names| {
696 let mut snapshot: Vec<String> = names
697 .into_iter()
698 .map(|mut name| {
699 name.make_ascii_lowercase();
700 if name.ends_with('.') {
701 name.pop();
702 }
703 name
704 })
705 .collect();
706 snapshot.sort();
707 snapshot.dedup();
708 if snapshot.is_empty() {
709 None
710 } else {
711 Some(Arc::new(snapshot))
712 }
713 }),
714 None => None,
715 };
716 debug_assert!(
724 tls_cert_names.is_none() || sni_owned.is_some(),
725 "cert-name snapshot must not exist without an SNI to key it"
726 );
727 debug_assert!(
728 tls_cert_names
729 .as_ref()
730 .is_none_or(|names| { !names.is_empty() && names.windows(2).all(|w| w[0] < w[1]) }),
731 "cert-name snapshot must be non-empty and strictly sorted (sorted + deduped)"
732 );
733
734 context.tls_server_name = sni_owned;
738 context.tls_cert_names = tls_cert_names;
739 context.tls_version = tls_version_label;
743 context.tls_cipher = tls_cipher_label;
744 context.tls_alpn = alpn_label;
745 let mut frontend = match alpn {
746 AlpnProtocol::Http11 => {
747 incr!(names::http::ALPN_HTTP11);
748 context.create_stream(handshake.request_id, 1 << 16)?;
749 mux::Connection::new_h1_server(
750 session_ulid,
751 front_stream,
752 handshake.container_frontend_timeout.duration(),
753 )
754 }
755 AlpnProtocol::H2 => {
756 incr!(names::http::ALPN_H2);
757 let flood_config = self.listener.borrow().get_h2_flood_config();
758 let connection_config = self.listener.borrow().get_h2_connection_config();
759 let stream_idle_timeout = self.listener.borrow().get_h2_stream_idle_timeout();
760 let graceful_shutdown_deadline =
761 self.listener.borrow().get_h2_graceful_shutdown_deadline();
762 mux::Connection::new_h2_server(
763 session_ulid,
764 front_stream,
765 &mut *context.buffers,
766 handshake.container_frontend_timeout.duration(),
767 flood_config,
768 connection_config,
769 stream_idle_timeout,
770 graceful_shutdown_deadline,
771 )?
772 }
773 };
774 frontend.readiness_mut().event.insert(frontend_events);
786
787 debug_assert!(
792 frontend.readiness_mut().event.is_writable()
793 && (!frontend_events.is_readable() || frontend.readiness_mut().event.is_readable()),
794 "post-handshake mux frontend must be armed for WRITABLE, and READABLE when due"
795 );
796 debug_assert_eq!(
800 context.session_ulid, session_ulid,
801 "mux context and connection must share the handshake-derived session ulid"
802 );
803
804 gauge_add!(names::protocol::HTTPS, 1);
805 Some(HttpsStateMachine::Mux(Mux {
806 configured_frontend_timeout: self.configured_frontend_timeout,
807 frontend_token: self.frontend_token,
808 frontend,
809 context,
810 router,
811 session_ulid,
812 timeouts: mux::MuxTimeouts::new(handshake.container_frontend_timeout),
816 backend_registry: mux::BackendRegistry::default(),
817 backends: Rc::clone(&self.proxy.borrow().backends),
821 }))
822 }
823
824 fn upgrade_mux(&self, mut mux: MuxTls) -> Option<HttpsStateMachine> {
825 debug!("{} mux switching to wss", log_context!(self));
826 let Some(stream) = mux.context.streams.pop() else {
827 error!(
828 "{} upgrade_mux: no stream attached to the TLS mux session, closing",
829 log_context!(self)
830 );
831 return None;
832 };
833 let mut container_frontend_timeout = mux.timeouts.frontend;
841 let (frontend_readiness, frontend_socket) = match mux.frontend {
842 mux::Connection::H1(mux::ConnectionH1 {
843 readiness, socket, ..
844 }) => (readiness, socket),
845 mux::Connection::H2(_) => {
846 error!(
847 "{} only h1<->h1 connections can upgrade to websocket",
848 log_context!(self)
849 );
850 return None;
851 }
852 };
853
854 let mux::StreamState::Linked(back_token) = stream.state else {
855 error!(
856 "{} upgrading stream should be linked to a backend",
857 log_context!(self)
858 );
859 return None;
860 };
861 let Some(backend) = mux.router.backends.remove(&back_token) else {
862 error!(
863 "{} upgrade_mux: backend for token {:?} is missing (already disconnected?), closing",
864 log_context!(self),
865 back_token
866 );
867 return None;
868 };
869 let mut container_backend_timeout = mux
870 .timeouts
871 .backends
872 .remove(&back_token)
873 .unwrap_or_else(|| TimeoutContainer::new_empty(mux.router.configured_backend_timeout));
874 let (cluster_id, backend_id, backend_readiness, backend_socket) = match backend {
875 mux::Connection::H1(mux::ConnectionH1 {
876 position: mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected),
877 readiness,
878 socket,
879 ..
880 }) => (cluster_id, backend, readiness, socket),
881 mux::Connection::H1(_) => {
882 error!(
883 "{} the backend disconnected just after upgrade, abort",
884 log_context!(self)
885 );
886 return None;
887 }
888 mux::Connection::H2(_) => {
889 error!(
890 "{} only h1<->h1 connections can upgrade to websocket",
891 log_context!(self)
892 );
893 return None;
894 }
895 };
896
897 let Some(backend) = mux.backend_registry.handle(&backend_id).cloned() else {
902 error!(
903 "{} upgrade_mux: backend {:?} names a slot this session never minted, closing",
904 log_context!(self),
905 backend_id.backend_id
906 );
907 return None;
908 };
909 let metric_incarnation = backend_id.metric_incarnation();
910 let backend_id = backend_id.backend_id.to_string();
911
912 let ws_context = stream.context.websocket_context();
913
914 container_frontend_timeout.reset();
915 container_backend_timeout.reset();
916
917 let backend_socket = backend_socket.stream;
920 let mut pipe = Pipe::new(
921 stream.back.storage.buffer,
922 Some(backend_id),
923 Some(backend_socket),
924 Some(backend),
925 Some(container_backend_timeout),
926 Some(container_frontend_timeout),
927 Some(cluster_id),
928 stream.front.storage.buffer,
929 self.frontend_token,
930 frontend_socket,
931 self.listener.clone(),
932 Protocol::HTTPS,
933 stream.context.session_id,
934 stream.context.id,
935 stream.context.session_address,
936 ws_context,
937 );
938 pipe.set_cluster_metrics_incarnation(metric_incarnation);
939
940 pipe.restore_readiness_events(frontend_readiness.event, backend_readiness.event);
941 pipe.set_back_token(back_token);
942 debug_assert!(
947 pipe.back_token().contains(&back_token),
948 "WSS pipe back token must be the connected backend token carried from the mux"
949 );
950 pipe.set_tls_metadata(
956 stream.context.tls_version,
957 stream.context.tls_cipher,
958 stream.context.tls_server_name.clone(),
959 stream.context.tls_alpn,
960 );
961
962 debug_assert_eq!(
968 pipe.frontend_readiness.event, frontend_readiness.event,
969 "WSS pipe must inherit the mux frontend readiness event verbatim"
970 );
971 debug_assert_eq!(
972 pipe.backend_readiness.event, backend_readiness.event,
973 "WSS pipe must inherit the mux backend readiness event verbatim"
974 );
975
976 gauge_add!(names::protocol::HTTPS, -1);
979 gauge_add!(names::protocol::WSS, 1);
980 gauge_add!(names::websocket::ACTIVE_REQUESTS, 1);
981 Some(HttpsStateMachine::WebSocket(pipe))
982 }
983
984 fn upgrade_websocket(
985 &self,
986 wss: Pipe<FrontRustls, HttpsListener>,
987 ) -> Option<HttpsStateMachine> {
988 error!(
990 "{} upgrade called on WSS, this should not happen",
991 log_context!(self)
992 );
993 Some(HttpsStateMachine::WebSocket(wss))
994 }
995
996 #[cfg(debug_assertions)]
1002 fn check_invariants(&self) {
1003 debug_assert!(
1007 !self.configured_frontend_timeout.is_zero()
1008 && !self.configured_backend_timeout.is_zero()
1009 && !self.configured_connect_timeout.is_zero(),
1010 "HTTPS session timeouts must stay non-zero for the session lifetime"
1011 );
1012 if self.state.failed() {
1017 debug_assert!(
1018 matches!(
1019 self.state.marker(),
1020 StateMarker::Expect
1021 | StateMarker::Handshake
1022 | StateMarker::Mux
1023 | StateMarker::WebSocket
1024 ),
1025 "FailedUpgrade must retain a valid origin-stage marker"
1026 );
1027 } else {
1028 debug_assert!(
1029 !self.state.failed(),
1030 "a non-failed session must not also report FailedUpgrade"
1031 );
1032 }
1033 debug_assert!(
1039 !matches!(self.state.marker(), StateMarker::Expect)
1040 || self.peer_address.is_none()
1041 || self.state.failed(),
1042 "a live Expect-stage session has no resolved peer address yet"
1043 );
1044 }
1045}
1046
1047impl ProxySession for HttpsSession {
1048 fn close(&mut self) {
1049 if self.has_been_closed {
1050 return;
1051 }
1052 debug_assert!(
1057 !self.has_been_closed,
1058 "close() body must run only on a not-yet-closed session"
1059 );
1060
1061 trace!("{} closing HTTPS session", log_context!(self));
1062 self.metrics.service_stop();
1063
1064 match self.state.marker() {
1066 StateMarker::Expect => gauge_add!(names::protocol::PROXY_EXPECT, -1),
1067 StateMarker::Handshake => gauge_add!(names::protocol::TLS_HANDSHAKE, -1),
1068 StateMarker::Mux => gauge_add!(names::protocol::HTTPS, -1),
1069 StateMarker::WebSocket => {
1070 gauge_add!(names::protocol::WSS, -1);
1071 gauge_add!(names::websocket::ACTIVE_REQUESTS, -1);
1072 }
1073 }
1074
1075 if self.state.failed() {
1076 match self.state.marker() {
1077 StateMarker::Expect => incr!(names::https::UPGRADE_EXPECT_FAILED),
1078 StateMarker::Handshake => incr!(names::https::UPGRADE_HANDSHAKE_FAILED),
1079 StateMarker::Mux => incr!(names::https::UPGRADE_MUX_FAILED),
1080 StateMarker::WebSocket => incr!(names::https::UPGRADE_WSS_FAILED),
1081 }
1082 self.state.close(self.proxy.clone(), &mut self.metrics);
1086 self.proxy.borrow().remove_session(self.frontend_token);
1087 self.has_been_closed = true;
1088 return;
1089 }
1090
1091 self.state.cancel_timeouts();
1092 self.state.close(self.proxy.clone(), &mut self.metrics);
1095
1096 let peer_closed = match &self.state {
1108 HttpsStateMachine::Mux(mux) => {
1109 mux.frontend_peer_closed()
1110 || match &mux.frontend {
1111 mux::Connection::H1(connection) => connection.socket.peer_disconnected,
1112 mux::Connection::H2(connection) => connection.socket.peer_disconnected,
1113 }
1114 }
1115 _ => false,
1116 };
1117 let front_socket = self.state.front_socket();
1118 if let Err(e) = mux::shutdown_write(front_socket, peer_closed) {
1119 if e.kind() != ErrorKind::NotConnected {
1121 error!(
1122 "{} error shutting down front socket({:?}): {:?}",
1123 log_context!(self),
1124 front_socket,
1125 e
1126 );
1127 }
1128 }
1129
1130 let proxy = self.proxy.borrow();
1139 proxy.remove_session(self.frontend_token);
1140
1141 self.has_been_closed = true;
1142 debug_assert!(
1146 self.has_been_closed,
1147 "close() must leave the session marked closed"
1148 );
1149 }
1150
1151 fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
1152 let session_result = self.state.timeout(token, &mut self.metrics);
1153 if session_result == StateResult::CloseSession {
1154 debug!(
1155 "{} HTTPS timeout requested close: token={:?}, marker={:?}",
1156 log_context!(self),
1157 token,
1158 self.state.marker()
1159 );
1160 }
1161 session_result == StateResult::CloseSession
1162 }
1163
1164 fn needs_ready_pass(&self) -> bool {
1165 matches!(&self.state, HttpsStateMachine::Mux(mux) if mux.has_pending_backend_failure())
1166 }
1167
1168 fn yielded(&mut self) -> bool {
1169 match &mut self.state {
1170 HttpsStateMachine::Mux(mux) => mux.take_loop_budget_yield(),
1171 _ => false,
1172 }
1173 }
1174
1175 fn protocol(&self) -> Protocol {
1176 Protocol::HTTPS
1177 }
1178
1179 fn update_readiness(&mut self, token: Token, events: Ready) {
1180 trace!(
1181 "{} token {:?} got event {}",
1182 log_context!(self),
1183 token,
1184 super::ready_to_string(events)
1185 );
1186 self.last_event = Instant::now();
1187 self.metrics.wait_start();
1188 if token == self.frontend_token {
1189 match &mut self.state {
1192 HttpsStateMachine::Mux(mux) => mux
1193 .frontend
1194 .socket_handler_mut()
1195 .readiness_delivered(events),
1196 HttpsStateMachine::WebSocket(pipe) => {
1197 pipe.front_socket_handler_mut().readiness_delivered(events)
1198 }
1199 HttpsStateMachine::Expect(..)
1201 | HttpsStateMachine::Handshake(_)
1202 | HttpsStateMachine::FailedUpgrade(_) => {}
1203 }
1204 }
1205 self.state.update_readiness(token, events);
1206 }
1207
1208 fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
1209 self.metrics.service_start();
1210
1211 let session_result =
1212 self.state
1213 .ready(session.clone(), self.proxy.clone(), &mut self.metrics);
1214
1215 let to_be_closed = match session_result {
1216 SessionResult::Close => true,
1217 SessionResult::Continue => false,
1218 SessionResult::Upgrade => match self.upgrade() {
1219 false => self.ready(session),
1220 true => true,
1221 },
1222 };
1223 if to_be_closed {
1224 debug!(
1225 "{} HTTPS ready requested close: marker={:?}",
1226 log_context!(self),
1227 self.state.marker()
1228 );
1229 }
1230
1231 #[cfg(debug_assertions)]
1235 self.check_invariants();
1236
1237 self.metrics.service_stop();
1238 to_be_closed
1239 }
1240
1241 fn shutting_down(&mut self) -> SessionIsToBeClosed {
1242 self.state.shutting_down()
1243 }
1244
1245 fn targets(&self, target: &crate::SessionTarget) -> bool {
1246 self.state.targets(target)
1247 }
1248
1249 fn last_event(&self) -> Instant {
1250 self.last_event
1251 }
1252
1253 fn print_session(&self) {
1254 self.state.print_state("HTTPS");
1255 error!("{} Metrics: {:?}", log_context!(self), self.metrics);
1256 }
1257
1258 fn frontend_token(&self) -> Token {
1259 self.frontend_token
1260 }
1261}
1262
1263pub type HostName = String;
1264pub type PathBegin = String;
1265
1266pub struct HttpsListener {
1267 active: bool,
1268 address: StdSocketAddr,
1269 answers: Rc<RefCell<HttpAnswers>>,
1270 config: HttpsListenerConfig,
1271 fronts: Router,
1272 listener: Option<MioTcpListener>,
1273 parked_listener: Option<MioTcpListener>,
1287 nodelay_backlog: bool,
1293 resolver: Arc<MutexCertificateResolver>,
1294 rustls_details: Arc<RustlsServerConfig>,
1295 tags: BTreeMap<String, CachedTags>,
1296 token: Token,
1297}
1298
1299impl ListenerHandler for HttpsListener {
1300 fn get_addr(&self) -> &StdSocketAddr {
1301 &self.address
1302 }
1303
1304 fn get_tags(&self, key: &str) -> Option<&CachedTags> {
1305 self.tags.get(key)
1306 }
1307
1308 fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
1309 match tags {
1310 Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
1311 None => self.tags.remove(&key),
1312 };
1313 }
1314
1315 fn protocol(&self) -> Protocol {
1316 Protocol::HTTPS
1317 }
1318
1319 fn public_address(&self) -> StdSocketAddr {
1320 self.config
1321 .public_address
1322 .map(|addr| addr.into())
1323 .unwrap_or(self.address)
1324 }
1325}
1326
1327impl L7ListenerHandler for HttpsListener {
1328 fn get_sticky_name(&self) -> &str {
1329 &self.config.sticky_name
1330 }
1331
1332 fn get_sozu_id_header(&self) -> &str {
1333 self.config
1334 .sozu_id_header
1335 .as_deref()
1336 .filter(|s| !s.is_empty())
1337 .unwrap_or("Sozu-Id")
1338 }
1339
1340 fn get_connect_timeout(&self) -> u32 {
1341 self.config.connect_timeout
1342 }
1343
1344 fn frontend_from_request(
1345 &self,
1346 host: &str,
1347 uri: &str,
1348 method: &Method,
1349 headers: &dyn RequestHeaders,
1350 ) -> Result<RouteResult, FrontendFromRequestError> {
1351 let start = Instant::now();
1352 let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
1353 Ok(tuple) => tuple,
1354 Err(parse_error) => {
1355 return Err(FrontendFromRequestError::HostParse {
1357 host: host.to_owned(),
1358 error: parse_error.to_string(),
1359 });
1360 }
1361 };
1362
1363 if remaining_input != &b""[..] {
1364 return Err(FrontendFromRequestError::InvalidCharsAfterHost(
1365 host.to_owned(),
1366 ));
1367 }
1368
1369 let host = unsafe { from_utf8_unchecked(hostname) };
1378
1379 let route = self
1380 .fronts
1381 .lookup_with_headers(host, uri, method, headers)
1382 .map_err(|e| {
1383 incr!(names::http::FAILED_BACKEND_MATCHING);
1384 FrontendFromRequestError::NoClusterFound(e)
1385 })?;
1386
1387 let now = Instant::now();
1388
1389 if let Some(cluster) = route.cluster_id.as_deref() {
1390 time!(
1391 names::event_loop::FRONTEND_MATCHING_TIME,
1392 cluster,
1393 (now - start).as_millis()
1394 );
1395 }
1396
1397 Ok(route)
1398 }
1399
1400 fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
1401 &self.answers
1402 }
1403
1404 fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
1405 crate::protocol::mux::H2FloodConfig::from_optional(
1413 self.config.h2_max_rst_stream_per_window,
1414 self.config.h2_max_ping_per_window,
1415 self.config.h2_max_settings_per_window,
1416 self.config.h2_max_empty_data_per_window,
1417 self.config.h2_max_window_update_stream0_per_window,
1418 self.config.h2_max_continuation_frames,
1419 self.config.h2_max_glitch_count,
1420 self.config.h2_max_rst_stream_lifetime,
1421 self.config.h2_max_rst_stream_abusive_lifetime,
1422 self.config.h2_max_rst_stream_emitted_lifetime,
1423 self.config.h2_max_header_list_size,
1424 self.config.h2_max_header_table_size,
1425 self.config.h2_max_header_fields,
1426 self.config.h2_stream_refusal_percent,
1427 )
1428 }
1429
1430 fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1431 crate::protocol::mux::H2ConnectionConfig::from_optional(
1432 self.config.h2_initial_connection_window,
1433 self.config.h2_max_concurrent_streams,
1434 self.config.h2_stream_shrink_ratio,
1435 )
1436 }
1437
1438 fn get_strict_sni_binding(&self) -> bool {
1439 self.config.strict_sni_binding.unwrap_or(true)
1454 }
1455
1456 fn get_elide_x_real_ip(&self) -> bool {
1457 self.config.elide_x_real_ip.unwrap_or(false)
1458 }
1459
1460 fn get_send_x_real_ip(&self) -> bool {
1461 self.config.send_x_real_ip.unwrap_or(false)
1462 }
1463
1464 fn get_forwarded_headers(&self) -> ForwardedHeaders {
1465 self.config.forwarded_headers()
1468 }
1469
1470 fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1471 let seconds = self
1478 .config
1479 .h2_stream_idle_timeout_seconds
1480 .map(|s| u64::from(s.max(1)))
1481 .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1482 std::time::Duration::from_secs(seconds)
1483 }
1484
1485 fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1486 match self.config.h2_graceful_shutdown_deadline_seconds {
1487 None => Some(std::time::Duration::from_secs(5)),
1488 Some(0) => None,
1489 Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1490 }
1491 }
1492}
1493
1494impl HttpsListener {
1495 pub fn key(&self) -> ListenerKey {
1498 ListenerKey::new(self.address, self.config.interface.as_deref())
1499 }
1500
1501 pub fn is(&self, key: &ListenerKey) -> bool {
1503 key.matches(&self.address, self.config.interface.as_deref())
1504 }
1505
1506 pub fn is_http11_disabled(&self) -> bool {
1512 self.config.disable_http11.unwrap_or(false)
1513 }
1514
1515 pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1521 &self.resolver
1522 }
1523
1524 pub fn try_new(
1525 config: HttpsListenerConfig,
1526 token: Token,
1527 ) -> Result<HttpsListener, ListenerError> {
1528 if let Some(v) = config.forwarded_headers {
1531 validate_forwarded_headers(v)?;
1532 }
1533 let resolver = Arc::new(MutexCertificateResolver::default());
1534
1535 let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1536
1537 let answers = Self::build_answers(&config)?;
1538
1539 Ok(HttpsListener {
1540 listener: None,
1541 parked_listener: None,
1542 nodelay_backlog: false,
1543 address: config.address.into(),
1544 resolver,
1545 rustls_details: server_config,
1546 active: false,
1547 fronts: Router::new(),
1548 answers: Rc::new(RefCell::new(answers)),
1549 config,
1550 token,
1551 tags: BTreeMap::new(),
1552 })
1553 }
1554
1555 fn build_answers(config: &HttpsListenerConfig) -> Result<HttpAnswers, ListenerError> {
1562 let mut answers_map = config.answers.clone();
1563 if let Some(ref legacy) = config.http_answers {
1564 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1565 }
1566 HttpAnswers::new(&answers_map)
1567 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))
1568 }
1569
1570 pub fn validate_config(config: &HttpsListenerConfig) -> Result<(), ListenerError> {
1581 Self::create_rustls_context(config, Arc::new(MutexCertificateResolver::default()))?;
1591 Self::build_answers(config)?;
1592 Ok(())
1593 }
1594
1595 pub fn activate(
1596 &mut self,
1597 registry: &Registry,
1598 tcp_listener: Option<MioTcpListener>,
1599 ) -> Result<Token, ListenerError> {
1600 if self.active {
1601 return Ok(self.token);
1602 }
1603 let address: StdSocketAddr = self.config.address.into();
1604
1605 let parked = self.parked_listener.take();
1611 let mut listener = match (tcp_listener, parked) {
1612 (Some(inherited), Some(parked)) => {
1613 warn!(
1621 "{} closing the socket parked on the HTTPS listener for {}: an \
1622 inherited socket arrived for the same address and is the one being \
1623 activated",
1624 log_module_context!(),
1625 address
1626 );
1627 drop(parked);
1628 inherited
1629 }
1630 (Some(inherited), None) => inherited,
1631 (None, Some(parked)) => parked,
1632 (None, None) => server_bind(address, self.config.interface.as_deref()).map_err(
1633 |server_bind_error| ListenerError::Activation {
1634 address,
1635 error: server_bind_error.to_string(),
1636 },
1637 )?,
1638 };
1639
1640 if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
1644 error!(
1645 "{} error setting nodelay on listen socket({:?}): {:?}",
1646 log_module_context!(),
1647 listener,
1648 e
1649 );
1650 }
1651
1652 let registration = registry
1653 .register(&mut listener, self.token, Interest::READABLE)
1654 .map_err(ListenerError::SocketRegistration);
1655 if let Err(error) = registration {
1656 self.parked_listener = Some(listener);
1666 return Err(error);
1667 }
1668
1669 self.listener = Some(listener);
1670 self.nodelay_backlog = true;
1671 self.active = true;
1672 debug_assert!(
1678 self.active && self.listener.is_some(),
1679 "an activated HTTPS listener must hold a bound socket and be flagged active"
1680 );
1681 Ok(self.token)
1682 }
1683
1684 pub fn create_rustls_context(
1685 config: &HttpsListenerConfig,
1686 resolver: Arc<MutexCertificateResolver>,
1687 ) -> Result<RustlsServerConfig, ListenerError> {
1688 let cipher_names = if config.cipher_list.is_empty() {
1689 DEFAULT_CIPHER_LIST.to_vec()
1690 } else {
1691 config
1692 .cipher_list
1693 .iter()
1694 .map(|s| s.as_str())
1695 .collect::<Vec<_>>()
1696 };
1697
1698 let ciphers = cipher_names
1699 .into_iter()
1700 .filter_map(|cipher| {
1701 cipher_suite_by_name(cipher).or_else(|| {
1702 error!(
1703 "{} unknown or unsupported cipher: {:?}",
1704 log_module_context!(),
1705 cipher
1706 );
1707 None
1708 })
1709 })
1710 .collect::<Vec<_>>();
1711
1712 let versions = config
1713 .versions
1714 .iter()
1715 .filter_map(|version| match TlsVersion::try_from(*version) {
1716 Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1717 Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1718 Ok(other_version) => {
1719 error!(
1720 "{} unsupported TLS version {:?}",
1721 log_module_context!(),
1722 other_version
1723 );
1724 None
1725 }
1726 Err(_) => {
1727 error!("{} unsupported TLS version", log_module_context!());
1728 None
1729 }
1730 })
1731 .collect::<Vec<_>>();
1732
1733 let kx_groups = if config.groups_list.is_empty() {
1734 default_provider().kx_groups
1735 } else {
1736 config
1737 .groups_list
1738 .iter()
1739 .filter_map(|group| match kx_group_by_name(group) {
1740 Some(kx) => Some(kx),
1741 None => {
1742 debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1743 None
1744 }
1745 })
1746 .collect::<Vec<_>>()
1747 };
1748
1749 let provider = Arc::new(CryptoProvider {
1750 cipher_suites: ciphers,
1751 kx_groups,
1752 ..default_provider()
1753 });
1754
1755 let builder = RustlsServerConfig::builder_with_provider(provider.clone())
1756 .with_protocol_versions(&versions[..])
1757 .map_err(|err| ListenerError::BuildRustls(err.to_string()))?;
1758
1759 let builder = match Self::client_cert_verifier(config, &provider)? {
1769 Some(verifier) => builder.with_client_cert_verifier(verifier),
1770 None => builder.with_no_client_auth(),
1771 };
1772
1773 let mut server_config = builder.with_cert_resolver(resolver);
1774 server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1775
1776 server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1777 DEFAULT_ALPN_PROTOCOLS
1778 .iter()
1779 .map(|p| p.as_bytes().to_vec())
1780 .collect()
1781 } else {
1782 config
1783 .alpn_protocols
1784 .iter()
1785 .map(|p| p.as_bytes().to_vec())
1786 .collect()
1787 };
1788
1789 Ok(server_config)
1790 }
1791
1792 fn check_crl_set(crls: &[CertificateRevocationListDer<'_>]) -> Result<(), ListenerError> {
1805 let mut scopes: Vec<(Vec<u8>, Option<Vec<u8>>)> = Vec::with_capacity(crls.len());
1806 for (index, crl) in crls.iter().enumerate() {
1807 let parsed = webpki::BorrowedCertRevocationList::from_der(crl.as_ref())
1808 .map_err(|e| ListenerError::ClientAuth(format!("invalid CRL: {e}")))?;
1809 let parsed = webpki::CertRevocationList::from(parsed);
1810 let issuer = parsed.issuer().to_vec();
1811 let distribution_point = parsed.issuing_distribution_point().map(<[u8]>::to_vec);
1812 let overlapping = scopes.iter().position(|(seen_issuer, seen_point)| {
1813 *seen_issuer == issuer
1814 && (seen_point.is_none()
1815 || distribution_point.is_none()
1816 || *seen_point == distribution_point)
1817 });
1818 if let Some(first) = overlapping {
1819 return Err(ListenerError::ClientAuth(format!(
1820 "CRLs #{} and #{} (counted across client_ca_crls) are issued by the \
1821 same CA for overlapping certificates, and only the first would be \
1822 consulted: configure one CRL per issuer, or one per distribution point",
1823 first + 1,
1824 index + 1
1825 )));
1826 }
1827 scopes.push((issuer, distribution_point));
1828 }
1829 Ok(())
1830 }
1831
1832 pub fn check_crls_current(crls: &[String], now: i64) -> Result<(), ListenerError> {
1844 let mut index = 0;
1845 for pem in crls {
1846 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1847 let crl =
1848 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?;
1849 index += 1;
1850 if let Some(next_update) = sozu_command::certificate::crl_next_update(crl.as_ref())
1851 && next_update <= now
1852 {
1853 return Err(ListenerError::ClientAuth(format!(
1854 "CRL #{index} (counted across client_ca_crls) expired {} seconds ago: \
1855 its nextUpdate is past, refresh it",
1856 now - next_update
1857 )));
1858 }
1859 }
1860 }
1861 Ok(())
1862 }
1863
1864 pub fn validate_client_auth_policy(policy: &ClientAuthPolicy) -> Result<(), ListenerError> {
1870 let mut config = HttpsListenerConfig::default();
1871 config.apply_client_auth_policy(policy);
1872 Self::client_cert_verifier(&config, &Arc::new(default_provider())).map(|_| ())
1873 }
1874
1875 fn client_cert_verifier(
1891 config: &HttpsListenerConfig,
1892 provider: &Arc<CryptoProvider>,
1893 ) -> Result<Option<Arc<dyn rustls::server::danger::ClientCertVerifier>>, ListenerError> {
1894 let raw = config
1896 .client_auth
1897 .unwrap_or(ClientAuthMode::ClientAuthNone as i32);
1898 let mode = ClientAuthMode::try_from(raw).map_err(|_| {
1899 ListenerError::ClientAuth(format!("unknown client_auth mode value {raw}"))
1900 })?;
1901 if mode == ClientAuthMode::ClientAuthNone {
1902 return Ok(None);
1903 }
1904
1905 let mut roots = RootCertStore::empty();
1912 for pem in &config.client_ca_certificates {
1913 let before = roots.len();
1914 for cert in CertificateDer::pem_slice_iter(pem.as_bytes()) {
1915 let cert =
1916 cert.map_err(|e| ListenerError::ClientAuth(format!("invalid CA PEM: {e}")))?;
1917 roots
1918 .add(cert)
1919 .map_err(|e| ListenerError::ClientAuth(format!("untrusted CA: {e}")))?;
1920 }
1921 if roots.len() == before {
1922 return Err(ListenerError::ClientAuth(
1923 "a configured trusted-CA entry contained no certificate".to_owned(),
1924 ));
1925 }
1926 }
1927 if roots.is_empty() {
1928 return Err(ListenerError::ClientAuth(
1929 "client auth requested but no trusted CA certificate was provided".to_owned(),
1930 ));
1931 }
1932
1933 let mut crls = Vec::new();
1938 for pem in &config.client_ca_crls {
1939 let before = crls.len();
1940 for crl in CertificateRevocationListDer::pem_slice_iter(pem.as_bytes()) {
1941 crls.push(
1942 crl.map_err(|e| ListenerError::ClientAuth(format!("invalid CRL PEM: {e}")))?,
1943 );
1944 }
1945 if crls.len() == before {
1946 return Err(ListenerError::ClientAuth(
1947 "a configured CRL entry contained no certificate revocation list".to_owned(),
1948 ));
1949 }
1950 }
1951 Self::check_crl_set(&crls)?;
1952
1953 let mut builder =
1954 WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone());
1955 if !crls.is_empty() {
1956 builder = builder.with_crls(crls).enforce_revocation_expiration();
1961 }
1962 if mode == ClientAuthMode::ClientAuthOptional {
1963 builder = builder.allow_unauthenticated();
1964 }
1965 let verifier = builder
1966 .build()
1967 .map_err(|e| ListenerError::ClientAuth(e.to_string()))?;
1968 Ok(Some(verifier))
1969 }
1970
1971 pub fn update_config(
1987 &mut self,
1988 patch: &UpdateHttpsListenerConfig,
1989 ) -> Result<(), ListenerError> {
1990 validate_h2_flood_knobs_https(patch)?;
1998 if let Some(ref alpn) = patch.alpn_protocols {
1999 validate_alpn_protocols(&alpn.values)?;
2000 }
2001 if let Some(ref hdr) = patch.sozu_id_header {
2002 validate_sozu_id_header(hdr)?;
2003 }
2004 if let Some(v) = patch.forwarded_headers {
2005 validate_forwarded_headers(v)?;
2006 }
2007 if patch.hsts.is_some_and(|hsts| hsts.enabled.is_none()) {
2014 return Err(ListenerError::HstsEnabledRequired);
2015 }
2016 HttpAnswers::validate_patch_templates(patch.http_answers.as_ref(), &patch.answers)
2017 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
2018
2019 let mut config = self.config.clone();
2020
2021 if let Some(v) = patch.public_address {
2023 config.public_address = Some(v);
2024 }
2025 if let Some(v) = patch.expect_proxy {
2026 config.expect_proxy = v;
2027 }
2028 if let Some(ref v) = patch.sticky_name {
2029 config.sticky_name = v.to_owned();
2030 }
2031 if let Some(v) = patch.front_timeout {
2032 config.front_timeout = v;
2033 }
2034 if let Some(v) = patch.back_timeout {
2035 config.back_timeout = v;
2036 }
2037 if let Some(v) = patch.connect_timeout {
2038 config.connect_timeout = v;
2039 }
2040 if let Some(v) = patch.request_timeout {
2041 config.request_timeout = v;
2042 }
2043 if let Some(v) = patch.strict_sni_binding {
2044 config.strict_sni_binding = Some(v);
2045 }
2046 if let Some(v) = patch.disable_http11 {
2047 config.disable_http11 = Some(v);
2048 }
2049 if let Some(ref v) = patch.sozu_id_header {
2050 config.sozu_id_header = Some(v.to_owned());
2051 }
2052 if let Some(v) = patch.elide_x_real_ip {
2053 config.elide_x_real_ip = Some(v);
2054 }
2055 if let Some(v) = patch.send_x_real_ip {
2056 config.send_x_real_ip = Some(v);
2057 }
2058 if let Some(v) = patch.forwarded_headers {
2059 config.forwarded_headers = Some(v);
2060 }
2061
2062 if let Some(v) = patch.h2_max_rst_stream_per_window {
2064 config.h2_max_rst_stream_per_window = Some(v);
2065 }
2066 if let Some(v) = patch.h2_max_ping_per_window {
2067 config.h2_max_ping_per_window = Some(v);
2068 }
2069 if let Some(v) = patch.h2_max_settings_per_window {
2070 config.h2_max_settings_per_window = Some(v);
2071 }
2072 if let Some(v) = patch.h2_max_empty_data_per_window {
2073 config.h2_max_empty_data_per_window = Some(v);
2074 }
2075 if let Some(v) = patch.h2_max_continuation_frames {
2076 config.h2_max_continuation_frames = Some(v);
2077 }
2078 if let Some(v) = patch.h2_max_glitch_count {
2079 config.h2_max_glitch_count = Some(v);
2080 }
2081 if let Some(v) = patch.h2_initial_connection_window {
2082 config.h2_initial_connection_window = Some(v);
2083 }
2084 if let Some(v) = patch.h2_max_concurrent_streams {
2085 config.h2_max_concurrent_streams = Some(v);
2086 }
2087 if let Some(v) = patch.h2_stream_shrink_ratio {
2088 config.h2_stream_shrink_ratio = Some(v);
2089 }
2090 if let Some(v) = patch.h2_max_rst_stream_lifetime {
2091 config.h2_max_rst_stream_lifetime = Some(v);
2092 }
2093 if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
2094 config.h2_max_rst_stream_abusive_lifetime = Some(v);
2095 }
2096 if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
2097 config.h2_max_rst_stream_emitted_lifetime = Some(v);
2098 }
2099 if let Some(v) = patch.h2_max_header_list_size {
2100 config.h2_max_header_list_size = Some(v);
2101 }
2102 if let Some(v) = patch.h2_max_header_table_size {
2103 config.h2_max_header_table_size = Some(v);
2104 }
2105 if let Some(v) = patch.h2_max_header_fields {
2106 config.h2_max_header_fields = Some(v);
2107 }
2108 if let Some(v) = patch.h2_stream_refusal_percent {
2109 config.h2_stream_refusal_percent = Some(v);
2110 }
2111 if let Some(v) = patch.h2_stream_idle_timeout_seconds {
2112 config.h2_stream_idle_timeout_seconds = Some(v);
2113 }
2114 if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
2115 config.h2_graceful_shutdown_deadline_seconds = Some(v);
2116 }
2117 if let Some(v) = patch.h2_max_window_update_stream0_per_window {
2118 config.h2_max_window_update_stream0_per_window = Some(v);
2119 }
2120
2121 if let Some(new_hsts) = patch.hsts {
2122 config.hsts = Some(new_hsts);
2123 }
2124 if let Some(ref alpn_wrapper) = patch.alpn_protocols {
2125 config.alpn_protocols = alpn_wrapper.values.clone();
2127 }
2128 if let Some(ref policy) = patch.client_auth_policy {
2129 config.apply_client_auth_policy(policy);
2130 }
2131
2132 let new_rustls = if patch.alpn_protocols.is_some() || patch.client_auth_policy.is_some() {
2144 Some(Arc::new(Self::create_rustls_context(
2145 &config,
2146 self.resolver.clone(),
2147 )?))
2148 } else {
2149 None
2150 };
2151
2152 let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
2157 let rebuilt = if answers_changed {
2158 if let Some(ref new_answers) = patch.http_answers {
2159 crate::sozu_command::state::merge_custom_http_answers(
2160 &mut config.http_answers,
2161 new_answers,
2162 );
2163 }
2164 for (code, body) in &patch.answers {
2165 if !body.is_empty() {
2166 config.answers.insert(code.clone(), body.clone());
2167 }
2168 }
2169
2170 let mut answers_map = config.answers.clone();
2171 if let Some(ref legacy) = config.http_answers {
2172 crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
2173 }
2174 Some(
2175 HttpAnswers::new(&answers_map)
2176 .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?,
2177 )
2178 } else {
2179 None
2180 };
2181
2182 self.config = config;
2184 if let Some(new_rustls) = new_rustls {
2185 self.rustls_details = new_rustls;
2186 }
2187 if let Some(alpn_wrapper) = &patch.alpn_protocols {
2188 debug_assert_eq!(
2193 self.config.alpn_protocols, alpn_wrapper.values,
2194 "committed ALPN config must match the patch values exactly"
2195 );
2196 }
2197 if let Some(mut rebuilt) = rebuilt {
2198 rebuilt.cluster_answers = self.answers.borrow().cluster_answers.clone();
2213 self.answers = Rc::new(RefCell::new(rebuilt));
2214 }
2215
2216 if patch.hsts.is_some() {
2233 let refreshed = self
2234 .fronts
2235 .refresh_inheriting_hsts(self.config.hsts.as_ref());
2236 for _ in 0..refreshed {
2237 crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
2238 }
2239 info!(
2240 "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
2241 frontend(s). Explicit per-frontend overrides untouched.",
2242 log_module_context!(),
2243 self.config.address,
2244 refreshed,
2245 );
2246 crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
2247 }
2248
2249 Ok(())
2250 }
2251
2252 pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2253 self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
2254 }
2255
2256 pub fn add_https_front_with_hsts_origin(
2263 &mut self,
2264 tls_front: HttpFrontend,
2265 hsts_origin: crate::router::HstsOrigin,
2266 ) -> Result<(), ListenerError> {
2267 self.fronts
2268 .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
2269 .map_err(ListenerError::AddFrontend)
2270 }
2271
2272 pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
2273 debug!(
2274 "{} removing tls_front {:?}",
2275 log_module_context!(),
2276 tls_front
2277 );
2278 self.fronts
2279 .remove_http_front(&tls_front)
2280 .map_err(ListenerError::RemoveFrontend)
2281 }
2282
2283 fn accept(&mut self) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2284 if let Some(ref sock) = self.listener {
2285 match sock.accept() {
2286 Ok((frontend_sock, peer)) => {
2287 if self.nodelay_backlog
2288 && let Err(e) = frontend_sock.set_nodelay(true)
2289 {
2290 error!(
2291 "{} error setting nodelay on front socket({:?}): {:?}",
2292 log_module_context!(),
2293 frontend_sock,
2294 e
2295 );
2296 }
2297 Ok((frontend_sock, peer))
2298 }
2299 Err(e) if e.kind() == ErrorKind::WouldBlock => {
2300 self.nodelay_backlog = false;
2302 Err(AcceptError::WouldBlock)
2303 }
2304 Err(e) => {
2305 error!("{} accept() IO error: {:?}", log_module_context!(), e);
2306 Err(AcceptError::IoError)
2307 }
2308 }
2309 } else {
2310 error!(
2311 "{} cannot accept connections, no listening socket available",
2312 log_module_context!()
2313 );
2314 Err(AcceptError::IoError)
2315 }
2316 }
2317}
2318
2319pub struct HttpsProxy {
2320 listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
2321 clusters: HashMap<ClusterId, Cluster>,
2322 backends: Rc<RefCell<BackendMap>>,
2323 pool: Rc<RefCell<Pool>>,
2324 registry: Registry,
2325 sessions: Rc<RefCell<SessionManager>>,
2326}
2327
2328impl HttpsProxy {
2329 pub fn new(
2330 registry: Registry,
2331 sessions: Rc<RefCell<SessionManager>>,
2332 pool: Rc<RefCell<Pool>>,
2333 backends: Rc<RefCell<BackendMap>>,
2334 ) -> HttpsProxy {
2335 HttpsProxy {
2336 listeners: HashMap::new(),
2337 clusters: HashMap::new(),
2338 backends,
2339 pool,
2340 registry,
2341 sessions,
2342 }
2343 }
2344
2345 pub fn add_listener(
2346 &mut self,
2347 config: HttpsListenerConfig,
2348 token: Token,
2349 ) -> Result<Token, ProxyError> {
2350 if self.listeners.contains_key(&token) {
2351 return Err(ProxyError::ListenerAlreadyPresent);
2352 }
2353 let mut https_listener =
2354 HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
2355 if let Some(sibling) = self
2363 .listeners_at(https_listener.address)
2364 .ok()
2365 .and_then(|listeners| listeners.into_iter().next())
2366 {
2367 let sibling = sibling.borrow();
2368 https_listener.fronts = sibling.fronts.clone();
2369 https_listener
2373 .fronts
2374 .refresh_inheriting_hsts(https_listener.config.hsts.as_ref());
2375 https_listener.tags = sibling.tags.clone();
2376 let certificates = sibling
2377 .resolver
2378 .0
2379 .lock()
2380 .map_err(|e| ProxyError::Lock(e.to_string()))?
2381 .clone();
2382 *https_listener
2383 .resolver
2384 .0
2385 .lock()
2386 .map_err(|e| ProxyError::Lock(e.to_string()))? = certificates;
2387 }
2388 match self.listeners.entry(token) {
2389 Entry::Vacant(entry) => {
2390 entry.insert(Rc::new(RefCell::new(https_listener)));
2391 Ok(token)
2392 }
2393 _ => Err(ProxyError::ListenerAlreadyPresent),
2394 }
2395 }
2396
2397 pub(crate) fn add_listener_with_retained_state(
2405 &mut self,
2406 config: HttpsListenerConfig,
2407 token: Token,
2408 retained_frontends: Vec<RequestHttpFrontend>,
2409 retained_certificates: Vec<AddCertificate>,
2410 ) -> Result<Token, ProxyError> {
2411 let address = config.address.into();
2412 let had_sibling = self.listeners_at(address).is_ok();
2413 self.add_listener(config, token)?;
2414
2415 if !had_sibling {
2416 for frontend in retained_frontends {
2417 if let Err(error) = self.add_https_frontend(frontend) {
2418 self.listeners.remove(&token);
2419 return Err(error);
2420 }
2421 }
2422 for certificate in retained_certificates {
2423 if let Err(error) = self.add_certificate(certificate) {
2424 self.listeners.remove(&token);
2425 return Err(error);
2426 }
2427 }
2428 }
2429
2430 Ok(token)
2431 }
2432
2433 pub fn remove_listener(
2434 &mut self,
2435 remove: RemoveListener,
2436 ) -> Result<Option<ResponseContent>, ProxyError> {
2437 let len = self.listeners.len();
2438
2439 let remove_address = remove.listener_key();
2440 self.listeners
2441 .retain(|_, listener| !listener.borrow().is(&remove_address));
2442
2443 if !self.listeners.len() < len {
2444 info!(
2445 "{} no HTTPS listener to remove at address {}",
2446 log_module_context!(),
2447 remove_address
2448 )
2449 }
2450 Ok(None)
2451 }
2452
2453 pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
2454 let listeners = std::mem::take(&mut self.listeners);
2455 let mut socket_errors = vec![];
2456 for l in listeners.values() {
2457 if let Some(mut sock) = l.borrow_mut().listener.take() {
2458 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2459 if let Err(e) = self.registry.deregister(&mut sock) {
2460 let error = format!("socket {sock:?}: {e:?}");
2461 socket_errors.push(error);
2462 }
2463 }
2464 }
2465
2466 if !socket_errors.is_empty() {
2467 return Err(ProxyError::SoftStop {
2468 proxy_protocol: "HTTPS".to_string(),
2469 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2470 });
2471 }
2472
2473 Ok(())
2474 }
2475
2476 pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
2477 let mut listeners = std::mem::take(&mut self.listeners);
2478 let mut socket_errors = vec![];
2479 for (_, l) in listeners.drain() {
2480 if let Some(mut sock) = l.borrow_mut().listener.take() {
2481 debug!("{} deregistering socket {:?}", log_module_context!(), sock);
2482 if let Err(e) = self.registry.deregister(&mut sock) {
2483 let error = format!("socket {sock:?}: {e:?}");
2484 socket_errors.push(error);
2485 }
2486 }
2487 }
2488
2489 if !socket_errors.is_empty() {
2490 return Err(ProxyError::HardStop {
2491 proxy_protocol: "HTTPS".to_string(),
2492 error: format!("Error deregistering listen sockets: {socket_errors:?}"),
2493 });
2494 }
2495
2496 Ok(())
2497 }
2498
2499 pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
2500 let certificates = self
2501 .listeners
2502 .values()
2503 .map(|listener| {
2504 let owned = listener.borrow();
2505 let resolver = owned
2510 .resolver
2511 .0
2512 .lock()
2513 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2514 let certificate_summaries = resolver
2515 .domains
2516 .to_hashmap()
2517 .drain()
2518 .map(|(k, fingerprint)| CertificateSummary {
2519 domain: certificate_summary_domain(&k),
2520 fingerprint: fingerprint.to_string(),
2521 })
2522 .collect();
2523
2524 Ok(CertificatesByAddress {
2525 address: owned.address.into(),
2526 certificate_summaries,
2527 })
2528 })
2529 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2530
2531 let listeners_count = certificates.len();
2532 let certificates_count = certificates
2533 .iter()
2534 .map(|entry| entry.certificate_summaries.len())
2535 .fold(0usize, |total, count| total.saturating_add(count));
2536
2537 info!(
2538 "{} got Certificates::All query, listeners_count={} certificates_count={}",
2539 log_module_context!(),
2540 listeners_count,
2541 certificates_count,
2542 );
2543
2544 Ok(Some(
2545 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2546 ))
2547 }
2548
2549 pub fn query_certificate_for_domain(
2550 &mut self,
2551 domain: String,
2552 ) -> Result<Option<ResponseContent>, ProxyError> {
2553 let domain_key = domain.to_ascii_lowercase();
2560 let certificates = self
2561 .listeners
2562 .values()
2563 .map(|listener| {
2564 let owned = listener.borrow();
2565 let resolver = owned
2568 .resolver
2569 .0
2570 .lock()
2571 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2572 let mut certificate_summaries = vec![];
2573
2574 if let Some((k, fingerprint)) = resolver.domain_lookup(domain_key.as_bytes(), true)
2575 {
2576 certificate_summaries.push(CertificateSummary {
2577 domain: certificate_summary_domain(k),
2578 fingerprint: fingerprint.to_string(),
2579 });
2580 }
2581 Ok(CertificatesByAddress {
2582 address: owned.address.into(),
2583 certificate_summaries,
2584 })
2585 })
2586 .collect::<Result<Vec<CertificatesByAddress>, ProxyError>>()?;
2587
2588 let listeners_count = certificates.len();
2589 let certificates_count = certificates
2590 .iter()
2591 .map(|entry| entry.certificate_summaries.len())
2592 .fold(0usize, |total, count| total.saturating_add(count));
2593
2594 info!(
2595 "{} got Certificates::Domain query, domain_bytes={} listeners_count={} certificates_count={}",
2596 log_module_context!(),
2597 domain.len(),
2598 listeners_count,
2599 certificates_count,
2600 );
2601
2602 Ok(Some(
2603 ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
2604 ))
2605 }
2606
2607 pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2615 use crate::InheritedSocketFate;
2616 match self
2617 .listeners
2618 .values()
2619 .find(|listener| listener.borrow().is(addr))
2620 {
2621 None => InheritedSocketFate::Unclaimed,
2622 Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2623 Some(_) => InheritedSocketFate::Adopted,
2624 }
2625 }
2626
2627 pub fn activate_listener(
2628 &mut self,
2629 addr: &ListenerKey,
2630 tcp_listener: Option<MioTcpListener>,
2631 ) -> Result<Token, ProxyError> {
2632 let listener = self
2633 .listeners
2634 .values()
2635 .find(|listener| listener.borrow().is(addr))
2636 .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2637
2638 listener
2639 .borrow_mut()
2640 .activate(&self.registry, tcp_listener)
2641 .map_err(|listener_error| ProxyError::ListenerActivation {
2642 address: addr.address,
2643 listener_error,
2644 })
2645 }
2646
2647 pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
2656 self.listeners
2657 .iter()
2658 .find(|(_, listener)| listener.borrow().is(address))
2659 .map(|(token, _)| *token)
2660 }
2661
2662 pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
2663 self.listeners
2664 .values()
2665 .filter_map(|listener| {
2666 let mut owned = listener.borrow_mut();
2667 if let Some(listener) = owned.listener.take() {
2668 owned.active = false;
2671 return Some((owned.key(), listener));
2672 }
2673
2674 None
2675 })
2676 .collect()
2677 }
2678
2679 pub fn give_back_listener(
2680 &mut self,
2681 address: &ListenerKey,
2682 ) -> Result<(Token, MioTcpListener), ProxyError> {
2683 let listener = self
2684 .listeners
2685 .values()
2686 .find(|listener| listener.borrow().is(address))
2687 .ok_or(ProxyError::NoListenerFound(address.clone()))?;
2688
2689 let mut owned = listener.borrow_mut();
2690
2691 let taken_listener = owned
2692 .listener
2693 .take()
2694 .ok_or(ProxyError::UnactivatedListener)?;
2695
2696 owned.active = false;
2699
2700 Ok((owned.token, taken_listener))
2701 }
2702
2703 fn listeners_at(
2707 &self,
2708 address: StdSocketAddr,
2709 ) -> Result<Vec<Rc<RefCell<HttpsListener>>>, ProxyError> {
2710 let mut listeners: Vec<_> = self
2711 .listeners
2712 .iter()
2713 .filter(|(_, listener)| listener.borrow().address == address)
2714 .map(|(token, listener)| (*token, listener.clone()))
2715 .collect();
2716 listeners.sort_by_key(|(token, _)| *token);
2718 let listeners: Vec<_> = listeners
2719 .into_iter()
2720 .map(|(_, listener)| listener)
2721 .collect();
2722 if listeners.is_empty() {
2723 return Err(ProxyError::NoListenerFound(address.into()));
2724 }
2725 Ok(listeners)
2726 }
2727
2728 pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
2730 let address: std::net::SocketAddr = patch.address.into();
2731 let listener = self
2732 .listeners
2733 .values()
2734 .find(|l| l.borrow().address == address)
2735 .ok_or(ProxyError::NoListenerFound(address.into()))?;
2736 listener
2737 .borrow_mut()
2738 .update_config(&patch)
2739 .map_err(|listener_error| ProxyError::ListenerActivation {
2740 address,
2741 listener_error,
2742 })
2743 }
2744
2745 pub fn add_cluster(
2746 &mut self,
2747 mut cluster: Cluster,
2748 ) -> Result<Option<ResponseContent>, ProxyError> {
2749 let mut cluster_overrides = cluster.answers.clone();
2750 if let Some(answer_503) = cluster.answer_503.take() {
2751 cluster_overrides
2752 .entry("503".to_owned())
2753 .or_insert(answer_503);
2754 }
2755 if !cluster_overrides.is_empty() {
2756 for listener in self.listeners.values() {
2757 listener
2758 .borrow()
2759 .answers
2760 .borrow_mut()
2761 .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
2762 .map_err(|(status, error)| {
2763 ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
2764 })?;
2765 }
2766 }
2767 self.clusters
2768 .insert(cluster.cluster_id.as_str().into(), cluster);
2769 Ok(None)
2770 }
2771
2772 pub fn remove_cluster(
2773 &mut self,
2774 cluster_id: &str,
2775 ) -> Result<Option<ResponseContent>, ProxyError> {
2776 self.clusters.remove(cluster_id);
2777 for listener in self.listeners.values() {
2778 listener
2779 .borrow()
2780 .answers
2781 .borrow_mut()
2782 .remove_cluster_answers(cluster_id);
2783 }
2784
2785 Ok(None)
2786 }
2787
2788 pub fn add_https_frontend(
2789 &mut self,
2790 front: RequestHttpFrontend,
2791 ) -> Result<Option<ResponseContent>, ProxyError> {
2792 let front = front.clone().to_frontend().map_err(|request_error| {
2793 ProxyError::WrongInputFrontend {
2794 front: Box::new(front),
2795 error: request_error.to_string(),
2796 }
2797 })?;
2798
2799 for listener in self.listeners_at(front.address)? {
2800 let mut listener = listener.borrow_mut();
2801 let mut front = front.clone();
2802
2803 let hsts_origin = if front.hsts.is_none() {
2820 front.hsts = listener.config.hsts;
2821 crate::router::HstsOrigin::InheritedFromListenerDefault
2822 } else {
2823 crate::router::HstsOrigin::Explicit
2824 };
2825
2826 listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
2827 listener
2828 .add_https_front_with_hsts_origin(front, hsts_origin)
2829 .map_err(ProxyError::AddFrontend)?;
2830 }
2831 Ok(None)
2832 }
2833
2834 pub fn remove_https_frontend(
2835 &mut self,
2836 front: RequestHttpFrontend,
2837 ) -> Result<Option<ResponseContent>, ProxyError> {
2838 let front = front.clone().to_frontend().map_err(|request_error| {
2839 ProxyError::WrongInputFrontend {
2840 front: Box::new(front),
2841 error: request_error.to_string(),
2842 }
2843 })?;
2844
2845 let mut result = Ok(None);
2849 for listener in self.listeners_at(front.address)? {
2850 let mut listener = listener.borrow_mut();
2851 let hostname = front.hostname.to_owned();
2852
2853 if let Err(error) = listener.remove_https_front(front.clone())
2854 && result.is_ok()
2855 {
2856 result = Err(ProxyError::RemoveFrontend(error));
2857 }
2858
2859 if !listener.fronts.has_hostname(&hostname) {
2860 listener.set_tags(hostname, None);
2861 }
2862 }
2863 result
2864 }
2865
2866 pub fn add_certificate(
2867 &mut self,
2868 add_certificate: AddCertificate,
2869 ) -> Result<Option<ResponseContent>, ProxyError> {
2870 let address = add_certificate.address.into();
2871
2872 for listener in self.listeners_at(address)? {
2873 let listener = listener.borrow();
2874 let mut resolver = listener
2875 .resolver
2876 .0
2877 .lock()
2878 .map_err(|e| ProxyError::Lock(e.to_string()))?;
2879
2880 resolver
2881 .add_certificate(&add_certificate)
2882 .map_err(ProxyError::AddCertificate)?;
2883 }
2884
2885 Ok(None)
2886 }
2887
2888 pub fn remove_certificate(
2890 &mut self,
2891 remove_certificate: RemoveCertificate,
2892 ) -> Result<Option<ResponseContent>, ProxyError> {
2893 let address = remove_certificate.address.into();
2894
2895 let fingerprint = Fingerprint(
2896 hex::decode(&remove_certificate.fingerprint)
2897 .map_err(ProxyError::WrongCertificateFingerprint)?,
2898 );
2899
2900 let mut result = Ok(None);
2904 for listener in self.listeners_at(address)? {
2905 let listener = listener.borrow();
2906 let applied = listener
2907 .resolver
2908 .0
2909 .lock()
2910 .map_err(|e| ProxyError::Lock(e.to_string()))
2911 .and_then(|mut resolver| {
2912 resolver
2913 .remove_certificate(&fingerprint)
2914 .map_err(ProxyError::RemoveCertificate)
2915 });
2916 if let Err(error) = applied
2917 && result.is_ok()
2918 {
2919 result = Err(error);
2920 }
2921 }
2922 result
2923 }
2924
2925 pub fn replace_certificate(
2927 &mut self,
2928 replace_certificate: ReplaceCertificate,
2929 ) -> Result<Option<ResponseContent>, ProxyError> {
2930 let address = replace_certificate.address.into();
2931
2932 let mut result = Ok(None);
2936 for listener in self.listeners_at(address)? {
2937 let listener = listener.borrow();
2938 let applied = listener
2939 .resolver
2940 .0
2941 .lock()
2942 .map_err(|e| ProxyError::Lock(e.to_string()))
2943 .and_then(|mut resolver| {
2944 resolver
2945 .replace_certificate(&replace_certificate)
2946 .map_err(ProxyError::ReplaceCertificate)
2947 });
2948 if let Err(error) = applied
2949 && result.is_ok()
2950 {
2951 result = Err(error);
2952 }
2953 }
2954 result
2955 }
2956}
2957
2958impl ProxyConfiguration for HttpsProxy {
2959 fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, StdSocketAddr), AcceptError> {
2960 match self.listeners.get(&Token(token.0)) {
2961 Some(listener) => listener.borrow_mut().accept(),
2962 None => Err(AcceptError::IoError),
2963 }
2964 }
2965
2966 fn create_session(
2967 &mut self,
2968 mut frontend_sock: MioTcpStream,
2969 peer: StdSocketAddr,
2970 token: ListenToken,
2971 wait_time: Duration,
2972 proxy: Rc<RefCell<Self>>,
2973 ) -> Result<(), AcceptError> {
2974 let listener = self
2975 .listeners
2976 .get(&Token(token.0))
2977 .ok_or(AcceptError::IoError)?;
2978 let owned = listener.borrow();
2983 let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
2984 error!(
2985 "{} failed to create server session: {:?}",
2986 log_module_context!(),
2987 e
2988 );
2989 AcceptError::IoError
2990 })?;
2991
2992 let mut session_manager = self.sessions.borrow_mut();
2993 let entry = session_manager.slab.vacant_entry();
2994 let session_token = Token(entry.key());
2995 debug_assert_eq!(
3000 session_token.0,
3001 entry.key(),
3002 "HTTPS session token must equal its slab key"
3003 );
3004
3005 self.registry
3006 .register(
3007 &mut frontend_sock,
3008 session_token,
3009 Interest::READABLE | Interest::WRITABLE,
3010 )
3011 .map_err(|register_error| {
3012 error!(
3013 "{} error registering front socket({:?}): {:?}",
3014 log_module_context!(),
3015 frontend_sock,
3016 register_error
3017 );
3018 AcceptError::RegisterError
3019 })?;
3020
3021 let public_address: StdSocketAddr = match owned.config.public_address {
3022 Some(pub_addr) => pub_addr.into(),
3023 None => owned.config.address.into(),
3024 };
3025
3026 let session = Rc::new(RefCell::new(HttpsSession::new(
3027 Duration::from_secs(owned.config.back_timeout as u64),
3028 Duration::from_secs(owned.config.connect_timeout as u64),
3029 Duration::from_secs(owned.config.front_timeout as u64),
3030 Duration::from_secs(owned.config.request_timeout as u64),
3031 owned.config.expect_proxy,
3032 listener.clone(),
3033 Rc::downgrade(&self.pool),
3034 proxy,
3035 public_address,
3036 rustls_details,
3037 frontend_sock,
3038 peer,
3039 session_token,
3040 wait_time,
3041 )));
3042 debug_assert_eq!(
3046 session.borrow().frontend_token(),
3047 session_token,
3048 "stored HTTPS session must report the slab token as its frontend token"
3049 );
3050 entry.insert(session);
3051
3052 Ok(())
3053 }
3054
3055 fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
3056 let request_id = request.id.clone();
3057
3058 let request_type = match request.content.request_type {
3059 Some(t) => t,
3060 None => return WorkerResponse::error(request_id, "Empty request"),
3061 };
3062
3063 let content_result = match request_type {
3064 RequestType::AddCluster(cluster) => {
3065 debug!(
3066 "{} {} add cluster {:?}",
3067 log_module_context!(),
3068 request_id,
3069 cluster
3070 );
3071 self.add_cluster(cluster)
3072 }
3073 RequestType::RemoveCluster(cluster_id) => {
3074 debug!(
3075 "{} {} remove cluster {:?}",
3076 log_module_context!(),
3077 request_id,
3078 cluster_id
3079 );
3080 self.remove_cluster(&cluster_id)
3081 }
3082 RequestType::AddHttpsFrontend(front)
3083 | RequestType::AddHttpsFrontendWithSilentClose(front)
3084 | RequestType::AddHttpsFrontendWithHeaderPredicates(front) => {
3085 debug!(
3086 "{} {} add https front {:?}",
3087 log_module_context!(),
3088 request_id,
3089 front
3090 );
3091 self.add_https_frontend(front)
3092 }
3093 RequestType::RemoveHttpsFrontend(front)
3094 | RequestType::RemoveHttpsFrontendWithHeaderPredicates(front) => {
3095 debug!(
3096 "{} {} remove https front {:?}",
3097 log_module_context!(),
3098 request_id,
3099 front
3100 );
3101 self.remove_https_frontend(front)
3102 }
3103 RequestType::AddCertificate(add_certificate) => {
3104 debug!(
3105 "{} {} add certificate: {:?}",
3106 log_module_context!(),
3107 request_id,
3108 add_certificate
3109 );
3110 self.add_certificate(add_certificate)
3111 }
3112 RequestType::RemoveCertificate(remove_certificate) => {
3113 debug!(
3114 "{} {} remove certificate: {:?}",
3115 log_module_context!(),
3116 request_id,
3117 remove_certificate
3118 );
3119 self.remove_certificate(remove_certificate)
3120 }
3121 RequestType::ReplaceCertificate(replace_certificate) => {
3122 debug!(
3123 "{} {} replace certificate: {:?}",
3124 log_module_context!(),
3125 request_id,
3126 replace_certificate
3127 );
3128 self.replace_certificate(replace_certificate)
3129 }
3130 RequestType::RemoveListener(remove) => {
3131 debug!(
3132 "{} removing HTTPS listener at address {:?}",
3133 log_module_context!(),
3134 remove.address
3135 );
3136 self.remove_listener(remove)
3137 }
3138 RequestType::SoftStop(_) => {
3139 debug!(
3140 "{} {} processing soft shutdown",
3141 log_module_context!(),
3142 request_id
3143 );
3144 match self.soft_stop() {
3145 Ok(_) => {
3146 info!(
3147 "{} {} soft stop successful",
3148 log_module_context!(),
3149 request_id
3150 );
3151 return WorkerResponse::processing(request.id);
3152 }
3153 Err(e) => Err(e),
3154 }
3155 }
3156 RequestType::HardStop(_) => {
3157 debug!(
3158 "{} {} processing hard shutdown",
3159 log_module_context!(),
3160 request_id
3161 );
3162 match self.hard_stop() {
3163 Ok(_) => {
3164 debug!(
3165 "{} {} hard stop successful",
3166 log_module_context!(),
3167 request_id
3168 );
3169 return WorkerResponse::processing(request.id);
3170 }
3171 Err(e) => Err(e),
3172 }
3173 }
3174 RequestType::Status(_) => {
3175 debug!("{} {} status", log_module_context!(), request_id);
3176 Ok(None)
3177 }
3178 RequestType::QueryCertificatesFromWorkers(filters) => {
3179 if let Some(domain) = filters.domain {
3180 debug!(
3181 "{} {} query certificate for domain_bytes={}",
3182 log_module_context!(),
3183 request_id,
3184 domain.len(),
3185 );
3186 self.query_certificate_for_domain(domain)
3187 } else {
3188 debug!(
3189 "{} {} query all certificates",
3190 log_module_context!(),
3191 request_id
3192 );
3193 self.query_all_certificates()
3194 }
3195 }
3196 other_request => {
3197 debug!(
3198 "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
3199 log_module_context!(),
3200 request.id,
3201 other_request
3202 );
3203 Err(ProxyError::UnsupportedMessage)
3204 }
3205 };
3206
3207 match content_result {
3208 Ok(content) => {
3209 debug!("{} {} successful", log_module_context!(), request_id);
3210 match content {
3211 Some(content) => WorkerResponse::ok_with_content(request_id, content),
3212 None => WorkerResponse::ok(request_id),
3213 }
3214 }
3215 Err(proxy_error) => {
3216 debug!(
3217 "{} {} unsuccessful: {}",
3218 log_module_context!(),
3219 request_id,
3220 proxy_error
3221 );
3222 WorkerResponse::error(request_id, proxy_error)
3223 }
3224 }
3225 }
3226}
3227impl L7Proxy for HttpsProxy {
3228 fn kind(&self) -> ListenerType {
3229 ListenerType::Https
3230 }
3231
3232 fn register_socket(
3233 &self,
3234 socket: &mut MioTcpStream,
3235 token: Token,
3236 interest: Interest,
3237 ) -> Result<(), std::io::Error> {
3238 self.registry.register(socket, token, interest)
3239 }
3240
3241 fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
3242 let mut session_manager = self.sessions.borrow_mut();
3243 let entry = session_manager.slab.vacant_entry();
3244 let token = Token(entry.key());
3245 let _entry = entry.insert(session);
3246 token
3247 }
3248
3249 fn remove_session(&self, token: Token) -> bool {
3250 let mut sessions = self.sessions.borrow_mut();
3251 sessions.untrack_all_cluster_ip(token);
3254 sessions.slab.try_remove(token.0).is_some()
3255 }
3256
3257 fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
3258 &self.clusters
3259 }
3260
3261 fn sessions(&self) -> Rc<RefCell<SessionManager>> {
3262 self.sessions.clone()
3263 }
3264}
3265
3266fn rustls_version_str(version: ProtocolVersion) -> &'static str {
3268 match version {
3269 ProtocolVersion::SSLv2 => "tls.version.SSLv2",
3270 ProtocolVersion::SSLv3 => "tls.version.SSLv3",
3271 ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
3272 ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
3273 ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
3274 ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
3275 ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
3276 ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
3277 ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
3278 ProtocolVersion::Unknown(_) => "tls.version.Unknown",
3279 _ => "tls.version.unimplemented",
3280 }
3281}
3282
3283pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
3290 match version {
3291 ProtocolVersion::SSLv2 => Some("SSLv2"),
3292 ProtocolVersion::SSLv3 => Some("SSLv3"),
3293 ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
3294 ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
3295 ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
3296 ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
3297 ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
3298 ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
3299 ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
3300 _ => None,
3301 }
3302}
3303
3304fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
3306 match cipher.suite() {
3307 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3308 "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
3309 }
3310 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3311 "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
3312 }
3313 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3314 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
3315 }
3316 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3317 "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
3318 }
3319 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3320 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
3321 }
3322 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3323 "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
3324 }
3325 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
3326 CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
3327 CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
3328 _ => "tls.cipher.Unsupported",
3329 }
3330}
3331
3332pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
3339 match cipher.suite() {
3340 CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
3341 Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
3342 }
3343 CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
3344 Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
3345 }
3346 CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
3347 Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
3348 }
3349 CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
3350 Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
3351 }
3352 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
3353 Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
3354 }
3355 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
3356 Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
3357 }
3358 CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
3359 CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
3360 CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
3361 _ => None,
3362 }
3363}
3364
3365pub mod testing {
3366 use crate::testing::*;
3367
3368 pub fn start_https_worker(
3370 config: HttpsListenerConfig,
3371 channel: ProxyChannel,
3372 max_buffers: usize,
3373 buffer_size: usize,
3374 ) -> anyhow::Result<()> {
3375 let address = config.address.into();
3376
3377 let ServerParts {
3378 event_loop,
3379 registry,
3380 sessions,
3381 pool,
3382 backends,
3383 client_scm_socket: _,
3384 server_scm_socket,
3385 server_config,
3386 } = prebuild_server(max_buffers, buffer_size, true)?;
3387
3388 let token = {
3389 let mut sessions = sessions.borrow_mut();
3390 let entry = sessions.slab.vacant_entry();
3391 let key = entry.key();
3392 let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3393 protocol: Protocol::HTTPSListen,
3394 })));
3395 Token(key)
3396 };
3397
3398 let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3399 proxy
3400 .add_listener(config, token)
3401 .with_context(|| "Failed at creating adding the listener")?;
3402 proxy
3403 .activate_listener(&address, None)
3404 .with_context(|| "Failed at creating activating the listener")?;
3405
3406 let mut server = Server::new(
3407 event_loop,
3408 channel,
3409 server_scm_socket,
3410 sessions,
3411 pool,
3412 backends,
3413 None,
3414 Some(proxy),
3415 None,
3416 server_config,
3417 None,
3418 false,
3419 )
3420 .with_context(|| "Failed at creating server")?;
3421
3422 debug!("{} starting event loop", log_module_context!());
3423 server.run();
3424 debug!("{} ending event loop", log_module_context!());
3425 Ok(())
3426 }
3427}
3428
3429#[cfg(test)]
3430mod tests {
3431 use std::sync::Arc;
3432
3433 use sozu_command::{
3434 config::ListenerBuilder,
3435 proto::command::{CertificateAndKey, SocketAddress},
3436 };
3437
3438 use super::*;
3439 use crate::{
3440 router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode},
3441 socket::SocketHandler,
3442 };
3443
3444 #[test]
3449 fn the_upgraded_frontend_reads_only_when_a_read_is_due() {
3450 assert_eq!(
3451 upgraded_frontend_events(Ready::EMPTY, false),
3452 Ready::WRITABLE,
3453 "a drained socket and an empty rustls need no read"
3454 );
3455 assert_eq!(
3456 upgraded_frontend_events(Ready::WRITABLE, false),
3457 Ready::WRITABLE
3458 );
3459 assert_eq!(
3460 upgraded_frontend_events(Ready::READABLE, false),
3461 Ready::READABLE | Ready::WRITABLE,
3462 "an edge not yet read must survive the upgrade"
3463 );
3464 assert_eq!(
3465 upgraded_frontend_events(Ready::EMPTY, true),
3466 Ready::READABLE | Ready::WRITABLE,
3467 "plaintext rustls already holds raises no edge"
3468 );
3469 assert_eq!(
3470 upgraded_frontend_events(Ready::READABLE | Ready::HUP, true),
3471 Ready::READABLE | Ready::WRITABLE | Ready::HUP,
3472 "a half-close seen during the handshake must reach the mux"
3473 );
3474 assert_eq!(
3475 upgraded_frontend_events(Ready::HUP, false),
3476 Ready::WRITABLE | Ready::HUP,
3477 "HUP survives even when no read is due"
3478 );
3479 assert_eq!(
3480 upgraded_frontend_events(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED, true),
3481 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::WRITE_CLOSED,
3482 "a hang-up seen during the handshake must reach the mux as one"
3483 );
3484 }
3485
3486 #[test]
3487 fn successful_tls_handshake_log_bounds_sni_and_alpn() {
3488 const SNI_SECRET: &str = "HTTPS_HANDSHAKE_SNI_SECRET_SENTINEL";
3489 const ALPN_SECRET: &str = "HTTPS_HANDSHAKE_ALPN_SECRET_SENTINEL";
3490
3491 let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
3492 let alpn = format!("{ALPN_SECRET}{}", "x".repeat(4096));
3493 let sni_len = sni.len();
3494 let alpn_len = alpn.len();
3495 let output = successful_tls_handshake_summary(Some(&sni), Some(&alpn));
3496
3497 for secret in [SNI_SECRET, ALPN_SECRET] {
3498 assert!(
3499 !output.contains(secret),
3500 "successful TLS handshake log leaked {secret}: {output}"
3501 );
3502 }
3503 for metadata in [
3504 format!("sni_bytes=Some({sni_len})"),
3505 format!("alpn_bytes=Some({alpn_len})"),
3506 ] {
3507 assert!(
3508 output.contains(&metadata),
3509 "successful TLS handshake log omitted {metadata}: {output}"
3510 );
3511 }
3512 assert!(
3513 output.len() <= 512,
3514 "successful TLS handshake log is not bounded: {} bytes",
3515 output.len()
3516 );
3517 }
3518
3519 fn proxy_with_certificate_domain(domain: String) -> HttpsProxy {
3520 let crate::testing::ServerParts {
3521 registry,
3522 sessions,
3523 pool,
3524 backends,
3525 ..
3526 } = crate::testing::prebuild_server(16, 16_384, false)
3527 .expect("test HTTPS proxy dependencies must initialize");
3528 let address = SocketAddress::new_v4(127, 0, 0, 1, crate::testing::provide_port());
3529 let mut config = ListenerBuilder::new_https(address)
3530 .to_tls(None)
3531 .expect("test HTTPS listener config must build");
3532 config.cipher_list = vec!["TLS13_AES_256_GCM_SHA384".to_owned()];
3533 config.groups_list = vec!["P-256".to_owned()];
3534 let mut proxy = HttpsProxy::new(registry, sessions, pool, backends);
3535 proxy
3536 .add_listener(config, Token(10))
3537 .expect("test HTTPS listener must be added");
3538 proxy
3539 .add_certificate(AddCertificate {
3540 address,
3541 certificate: CertificateAndKey {
3542 certificate: include_str!("../assets/certificate.pem").to_owned(),
3543 key: include_str!("../assets/key.pem").to_owned(),
3544 certificate_chain: Vec::new(),
3545 versions: Vec::new(),
3546 names: vec![domain],
3547 },
3548 expired_at: None,
3549 })
3550 .expect("test certificate must be added");
3551 proxy
3552 }
3553
3554 #[test]
3568 fn a_poisoned_resolver_fails_the_certificate_queries_instead_of_the_worker() {
3569 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3570
3571 let resolver = proxy
3572 .listeners
3573 .values()
3574 .next()
3575 .expect("the test proxy has one listener")
3576 .borrow()
3577 .resolver()
3578 .clone();
3579
3580 let poisoner = resolver.clone();
3584 let unwound = std::thread::spawn(move || {
3585 let _guard = poisoner.0.lock().expect("first lock is not poisoned");
3586 panic!("simulated worker panic while holding the resolver lock");
3587 })
3588 .join();
3589 assert!(unwound.is_err(), "the poisoning thread must have unwound");
3590 assert!(
3591 resolver.0.lock().is_err(),
3592 "the resolver mutex must now be poisoned"
3593 );
3594
3595 assert!(
3596 matches!(proxy.query_all_certificates(), Err(ProxyError::Lock(_))),
3597 "Certificates::All on a poisoned resolver must report a lock error"
3598 );
3599 assert!(
3600 matches!(
3601 proxy.query_certificate_for_domain("lolcatho.st".to_owned()),
3602 Err(ProxyError::Lock(_))
3603 ),
3604 "Certificates::Domain on a poisoned resolver must report a lock error"
3605 );
3606 }
3607
3608 #[test]
3623 fn a_non_utf8_resolver_key_is_listed_lossily_not_panicked() {
3624 let mut proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
3625
3626 let resolver = proxy
3627 .listeners
3628 .values()
3629 .next()
3630 .expect("the test proxy has one listener")
3631 .borrow()
3632 .resolver()
3633 .clone();
3634 {
3635 let mut owned = resolver.0.lock().expect("resolver lock");
3636 let inserted = owned
3637 .domains
3638 .insert(vec![0xff, 0xfe], Fingerprint(vec![0x01, 0x02]));
3639 assert_ne!(
3640 inserted,
3641 crate::router::pattern_trie::InsertResult::Failed,
3642 "the SNI trie must accept the non-UTF-8 key this test relies on"
3643 );
3644 }
3645
3646 let response = proxy
3647 .query_all_certificates()
3648 .expect("a non-UTF-8 trie key must not fail the query");
3649 assert!(
3650 response.is_some(),
3651 "Certificates::All must still answer with a listing"
3652 );
3653 }
3654
3655 #[test]
3656 fn query_all_certificates_log_redacts_response_domains() {
3657 const DOMAIN_SECRET: &str = "QUERY_ALL_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3658
3659 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3663 let output = crate::capture_test_logs(move || {
3664 let mut proxy = proxy_with_certificate_domain(domain);
3665 proxy
3666 .query_all_certificates()
3667 .expect("certificate query must succeed");
3668 });
3669
3670 assert!(
3671 !output.contains(DOMAIN_SECRET),
3672 "Certificates::All log leaked response domain {DOMAIN_SECRET}"
3673 );
3674 for metadata in ["listeners_count=1", "certificates_count=1"] {
3675 assert!(
3676 output.contains(metadata),
3677 "Certificates::All log omitted bounded metadata {metadata}: {output}"
3678 );
3679 }
3680 assert!(
3681 output.len() <= 1024,
3682 "Certificates::All log capture is not bounded: {} bytes",
3683 output.len()
3684 );
3685 }
3686
3687 #[test]
3688 fn query_certificate_for_domain_log_redacts_request_and_response_domain() {
3689 const DOMAIN_SECRET: &str = "QUERY_ONE_CERTIFICATE_DOMAIN_SECRET_SENTINEL";
3690
3691 let domain = format!("{DOMAIN_SECRET}{}", "x".repeat(2048));
3694 let domain_len = domain.len();
3695 let output = crate::capture_test_logs_at_level("debug", move || {
3696 let mut proxy = proxy_with_certificate_domain(domain.clone());
3697 let response = proxy.notify(WorkerRequest {
3698 id: "query-domain-redaction-test".to_owned(),
3699 content: RequestType::QueryCertificatesFromWorkers(
3700 sozu_command::proto::command::QueryCertificatesFilters {
3701 domain: Some(domain),
3702 fingerprint: None,
3703 },
3704 )
3705 .into(),
3706 });
3707 assert_eq!(
3708 response.status,
3709 sozu_command::proto::command::ResponseStatus::Ok as i32,
3710 "domain certificate query must succeed: {}",
3711 response.message
3712 );
3713 });
3714
3715 assert!(
3716 !output.contains(DOMAIN_SECRET),
3717 "Certificates::Domain log leaked request or response domain {DOMAIN_SECRET}"
3718 );
3719 for metadata in [
3720 format!("domain_bytes={domain_len}"),
3721 "listeners_count=1".to_owned(),
3722 "certificates_count=1".to_owned(),
3723 ] {
3724 assert!(
3725 output.contains(&metadata),
3726 "Certificates::Domain log omitted bounded metadata {metadata}: {output}"
3727 );
3728 }
3729 assert!(
3730 output.len() <= 1024,
3731 "Certificates::Domain log capture is not bounded: {} bytes",
3732 output.len()
3733 );
3734 }
3735
3736 #[test]
3737 fn frontend_from_request_test() {
3738 let cluster_id1 = ClusterId::from("cluster_1");
3739 let cluster_id2 = ClusterId::from("cluster_2");
3740 let cluster_id3 = ClusterId::from("cluster_3");
3741 let uri1 = "/".to_owned();
3742 let uri2 = "/yolo".to_owned();
3743 let uri3 = "/yolo/swag".to_owned();
3744
3745 let mut fronts = Router::new();
3746 assert!(fronts.add_tree_rule(
3747 "lolcatho.st".as_bytes(),
3748 &PathRule::Prefix(uri1),
3749 &MethodRule::new(None),
3750 &Route::ClusterId(cluster_id1.clone())
3751 ));
3752 assert!(fronts.add_tree_rule(
3753 "lolcatho.st".as_bytes(),
3754 &PathRule::Prefix(uri2),
3755 &MethodRule::new(None),
3756 &Route::ClusterId(cluster_id2)
3757 ));
3758 assert!(fronts.add_tree_rule(
3759 "lolcatho.st".as_bytes(),
3760 &PathRule::Prefix(uri3),
3761 &MethodRule::new(None),
3762 &Route::ClusterId(cluster_id3)
3763 ));
3764 assert!(fronts.add_tree_rule(
3765 "other.domain".as_bytes(),
3766 &PathRule::Prefix("test".to_string()),
3767 &MethodRule::new(None),
3768 &Route::ClusterId(cluster_id1)
3769 ));
3770
3771 let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
3772 let resolver = Arc::new(MutexCertificateResolver::default());
3773
3774 let crypto_provider = Arc::new(default_provider());
3775
3776 let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
3777 .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
3778 .expect("could not create rustls config server")
3779 .with_no_client_auth()
3780 .with_cert_resolver(resolver.clone());
3781
3782 let rustls_details = Arc::new(server_config);
3783
3784 let default_config = ListenerBuilder::new_https(address)
3785 .to_tls(None)
3786 .expect("Could not create default HTTPS listener config");
3787
3788 println!("it doesn't even matter");
3789
3790 let listener = HttpsListener {
3791 parked_listener: None,
3792 nodelay_backlog: false,
3793 listener: None,
3794 address: address.into(),
3795 fronts,
3796 rustls_details,
3797 resolver,
3798 answers: Rc::new(RefCell::new(
3799 HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
3800 )),
3801 config: default_config,
3802 token: Token(0),
3803 active: true,
3804 tags: BTreeMap::new(),
3805 };
3806
3807 println!("TEST {}", line!());
3808 let frontend1 = listener.frontend_from_request(
3809 "lolcatho.st",
3810 "/",
3811 &Method::Get,
3812 &crate::router::NoHeaders,
3813 );
3814 assert_eq!(
3815 frontend1
3816 .expect("should find a frontend")
3817 .cluster_id
3818 .as_deref(),
3819 Some("cluster_1")
3820 );
3821 println!("TEST {}", line!());
3822 let frontend2 = listener.frontend_from_request(
3823 "lolcatho.st",
3824 "/test",
3825 &Method::Get,
3826 &crate::router::NoHeaders,
3827 );
3828 assert_eq!(
3829 frontend2
3830 .expect("should find a frontend")
3831 .cluster_id
3832 .as_deref(),
3833 Some("cluster_1")
3834 );
3835 println!("TEST {}", line!());
3836 let frontend3 = listener.frontend_from_request(
3837 "lolcatho.st",
3838 "/yolo/test",
3839 &Method::Get,
3840 &crate::router::NoHeaders,
3841 );
3842 assert_eq!(
3843 frontend3
3844 .expect("should find a frontend")
3845 .cluster_id
3846 .as_deref(),
3847 Some("cluster_2")
3848 );
3849 println!("TEST {}", line!());
3850 let frontend4 = listener.frontend_from_request(
3851 "lolcatho.st",
3852 "/yolo/swag",
3853 &Method::Get,
3854 &crate::router::NoHeaders,
3855 );
3856 assert_eq!(
3857 frontend4
3858 .expect("should find a frontend")
3859 .cluster_id
3860 .as_deref(),
3861 Some("cluster_3")
3862 );
3863 println!("TEST {}", line!());
3864 let frontend5 =
3865 listener.frontend_from_request("domain", "/", &Method::Get, &crate::router::NoHeaders);
3866 assert!(frontend5.is_err());
3867 }
3869
3870 #[test]
3871 fn wildcard_certificate_names() {
3872 let mut trie = TrieNode::root();
3873
3874 trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
3875 trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
3876 trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
3877 trie.domain_insert(
3878 "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
3879 3u8,
3880 );
3881 trie.domain_insert(
3882 "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
3883 4u8,
3884 );
3885
3886 let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
3887 println!("query result: {res:?}");
3888
3889 assert_eq!(
3890 trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
3891 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3892 );
3893 assert_eq!(
3894 trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
3895 Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
3896 );
3897 }
3898
3899 #[test]
3900 fn wildcard_with_subdomains() {
3901 let mut trie = TrieNode::root();
3902
3903 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3904 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3905
3906 let res = trie.domain_lookup(b"sub.test.example.com", true);
3907 println!("query result: {res:?}");
3908
3909 assert_eq!(
3910 trie.domain_lookup(b"sub.test.example.com", true),
3911 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3912 );
3913 assert_eq!(
3914 trie.domain_lookup(b"hello.sub.test.example.com", true),
3915 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3916 );
3917
3918 let mut trie = TrieNode::root();
3920
3921 trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
3922 trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
3923
3924 let res = trie.domain_lookup(b"sub.test.example.com", true);
3925 println!("query result: {res:?}");
3926
3927 assert_eq!(
3928 trie.domain_lookup(b"sub.test.example.com", true),
3929 Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
3930 );
3931 assert_eq!(
3932 trie.domain_lookup(b"hello.sub.test.example.com", true),
3933 Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
3934 );
3935 }
3936
3937 #[test]
3940 fn a_listener_with_an_unknown_forwarded_headers_value_is_refused() {
3941 let build = |forwarded_headers: Option<i32>| {
3942 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1043))
3943 .to_tls(None)
3944 .expect("default HTTPS listener config");
3945 cfg.forwarded_headers = forwarded_headers;
3946 HttpsListener::try_new(cfg, Token(0))
3947 };
3948 assert!(build(Some(-1)).is_err(), "an unknown mode must be refused");
3949 let listener = build(Some(ForwardedHeaders::None as i32)).expect("a known mode");
3950 assert_eq!(listener.get_forwarded_headers(), ForwardedHeaders::None);
3951 }
3952
3953 #[test]
3959 fn get_h2_flood_config_maps_every_listener_knob_to_its_own_threshold() {
3960 let mut cfg = ListenerBuilder::new_https(SocketAddress::new_v4(127, 0, 0, 1, 1042))
3961 .to_tls(None)
3962 .expect("default HTTPS listener config");
3963 cfg.h2_max_rst_stream_per_window = Some(101);
3964 cfg.h2_max_ping_per_window = Some(102);
3965 cfg.h2_max_settings_per_window = Some(103);
3966 cfg.h2_max_empty_data_per_window = Some(104);
3967 cfg.h2_max_window_update_stream0_per_window = Some(105);
3968 cfg.h2_max_continuation_frames = Some(106);
3969 cfg.h2_max_glitch_count = Some(107);
3970 cfg.h2_max_rst_stream_lifetime = Some(108);
3971 cfg.h2_max_rst_stream_abusive_lifetime = Some(109);
3972 cfg.h2_max_rst_stream_emitted_lifetime = Some(110);
3973 cfg.h2_max_header_list_size = Some(111);
3974 cfg.h2_max_header_table_size = Some(112);
3975 cfg.h2_max_header_fields = Some(113);
3976 cfg.h2_stream_refusal_percent = Some(114);
3977
3978 let listener = HttpsListener::try_new(cfg, Token(0)).expect("build listener");
3979 let flood = listener.get_h2_flood_config();
3980
3981 assert_eq!(flood.max_rst_stream_per_window(), 101);
3982 assert_eq!(flood.max_ping_per_window(), 102);
3983 assert_eq!(flood.max_settings_per_window(), 103);
3984 assert_eq!(flood.max_empty_data_per_window(), 104);
3985 assert_eq!(flood.max_window_update_stream0_per_window(), 105);
3986 assert_eq!(flood.max_continuation_frames(), 106);
3987 assert_eq!(flood.max_glitch_count(), 107);
3988 assert_eq!(flood.max_rst_stream_lifetime(), 108);
3989 assert_eq!(flood.max_rst_stream_abusive_lifetime(), 109);
3990 assert_eq!(flood.max_rst_stream_emitted_lifetime(), 110);
3991 assert_eq!(flood.max_header_list_size(), 111);
3992 assert_eq!(flood.max_header_table_size(), 112);
3993 assert_eq!(flood.max_header_fields(), 113);
3994 assert_eq!(flood.stream_refusal_percent(), 114);
3995 }
3996
3997 #[test]
3998 fn h2_stream_idle_timeout_inherits_back_timeout() {
3999 use std::time::Duration;
4000
4001 let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
4002 let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
4003 let mut cfg = ListenerBuilder::new_https(address)
4004 .to_tls(None)
4005 .expect("default HTTPS listener config");
4006 cfg.back_timeout = back_timeout;
4007 cfg.h2_stream_idle_timeout_seconds = explicit;
4008 HttpsListener::try_new(cfg, Token(0)).expect("build listener")
4009 };
4010
4011 assert_eq!(
4013 build(180, None).get_h2_stream_idle_timeout(),
4014 Duration::from_secs(180)
4015 );
4016
4017 assert_eq!(
4019 build(5, None).get_h2_stream_idle_timeout(),
4020 Duration::from_secs(30)
4021 );
4022
4023 assert_eq!(
4025 build(180, Some(10)).get_h2_stream_idle_timeout(),
4026 Duration::from_secs(10)
4027 );
4028 assert_eq!(
4029 build(5, Some(600)).get_h2_stream_idle_timeout(),
4030 Duration::from_secs(600)
4031 );
4032
4033 assert_eq!(
4035 build(180, Some(0)).get_h2_stream_idle_timeout(),
4036 Duration::from_secs(1)
4037 );
4038 }
4039
4040 const PROXY_ADVERTISED_PEER: &str = "10.0.0.42:12345";
4045
4046 #[test]
4077 fn front_rustls_peer_snapshot_is_the_session_peer_not_the_accepted_socket() {
4078 let advertised: StdSocketAddr = PROXY_ADVERTISED_PEER
4079 .parse()
4080 .expect("the advertised peer literal must parse");
4081
4082 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4083 let listener = proxy
4084 .listeners
4085 .values()
4086 .next()
4087 .expect("the test proxy has one listener")
4088 .clone();
4089 let pool = Rc::downgrade(&proxy.pool);
4090 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4091 .expect("a test rustls server session must be created");
4092 let public_address = *listener.borrow().get_addr();
4093 let proxy = Rc::new(RefCell::new(proxy));
4094
4095 let balancer = std::net::TcpListener::bind("127.0.0.1:0")
4100 .expect("test listener must bind to a loopback port");
4101 let balancer_peer = balancer
4102 .local_addr()
4103 .expect("test listener must report its local address");
4104 let stream =
4105 std::net::TcpStream::connect(balancer_peer).expect("loopback connect must complete");
4106 stream
4107 .set_nonblocking(true)
4108 .expect("mio requires a nonblocking stream");
4109 let stream = MioTcpStream::from_std(stream);
4110
4111 let mut session = HttpsSession::new(
4112 Duration::from_secs(30),
4113 Duration::from_secs(30),
4114 Duration::from_secs(30),
4115 Duration::from_secs(30),
4116 false,
4119 listener,
4120 pool,
4121 proxy,
4122 public_address,
4123 rustls_details,
4124 stream,
4125 balancer_peer,
4128 Token(0),
4129 Duration::from_secs(0),
4130 );
4131
4132 assert_eq!(
4133 session.peer_address,
4134 Some(balancer_peer),
4135 "a direct-route session starts out with the accepted socket's peer"
4136 );
4137
4138 let mut handshake = match session.state.take() {
4139 HttpsStateMachine::Handshake(handshake) => handshake,
4140 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4141 };
4142
4143 session.peer_address = Some(advertised);
4145 handshake.peer_address = Some(advertised);
4146 assert_ne!(
4147 advertised, balancer_peer,
4148 "the advertised and connection peers must differ for this test to discriminate"
4149 );
4150
4151 let upgraded = session
4152 .upgrade_handshake(handshake)
4153 .expect("the handshake must upgrade to a mux session");
4154 let (snapshot, live_lookup) = match &upgraded {
4157 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4158 mux::Connection::H1(connection) => (
4159 connection.socket.peer_addr(),
4160 connection.socket.socket_ref().peer_addr().ok(),
4161 ),
4162 mux::Connection::H2(connection) => (
4163 connection.socket.peer_addr(),
4164 connection.socket.socket_ref().peer_addr().ok(),
4165 ),
4166 },
4167 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4168 };
4169
4170 assert_eq!(
4173 live_lookup,
4174 Some(balancer_peer),
4175 "the frontend socket must still be connected to the stand-in balancer"
4176 );
4177 assert_eq!(
4178 snapshot,
4179 Some(advertised),
4180 "FrontRustls must carry the PROXY-advertised source, not the accepted socket's peer"
4181 );
4182 }
4183
4184 #[test]
4202 fn front_rustls_peer_addr_falls_back_to_the_live_lookup() {
4203 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4204 let listener = proxy
4205 .listeners
4206 .values()
4207 .next()
4208 .expect("the test proxy has one listener")
4209 .clone();
4210 let pool = Rc::downgrade(&proxy.pool);
4211 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4212 .expect("a test rustls server session must be created");
4213 let public_address = *listener.borrow().get_addr();
4214 let proxy = Rc::new(RefCell::new(proxy));
4215
4216 let peer = std::net::TcpListener::bind("127.0.0.1:0")
4217 .expect("test listener must bind to a loopback port");
4218 let live_peer = peer
4219 .local_addr()
4220 .expect("test listener must report its local address");
4221 let stream =
4222 std::net::TcpStream::connect(live_peer).expect("loopback connect must complete");
4223 stream
4224 .set_nonblocking(true)
4225 .expect("mio requires a nonblocking stream");
4226 let stream = MioTcpStream::from_std(stream);
4227
4228 let mut session = HttpsSession::new(
4229 Duration::from_secs(30),
4230 Duration::from_secs(30),
4231 Duration::from_secs(30),
4232 Duration::from_secs(30),
4233 false,
4234 listener,
4235 pool,
4236 proxy,
4237 public_address,
4238 rustls_details,
4239 stream,
4240 live_peer,
4241 Token(0),
4242 Duration::from_secs(0),
4243 );
4244
4245 let mut handshake = match session.state.take() {
4246 HttpsStateMachine::Handshake(handshake) => handshake,
4247 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4248 };
4249
4250 session.peer_address = None;
4252 handshake.peer_address = None;
4253
4254 let upgraded = session
4255 .upgrade_handshake(handshake)
4256 .expect("the handshake must upgrade to a mux session");
4257 let snapshot = match &upgraded {
4258 HttpsStateMachine::Mux(mux) => match &mux.frontend {
4259 mux::Connection::H1(connection) => connection.socket.peer_addr(),
4260 mux::Connection::H2(connection) => connection.socket.peer_addr(),
4261 },
4262 _ => panic!("a completed TLS handshake must upgrade into the mux state"),
4263 };
4264
4265 assert_eq!(
4266 snapshot,
4267 Some(live_peer),
4268 "with nothing cached, FrontRustls::peer_addr must degrade to getpeername(2)"
4269 );
4270 }
4271
4272 #[test]
4289 fn query_certificate_for_domain_answers_any_spelling_of_the_name() {
4290 let mut proxy = proxy_with_certificate_domain("MiXeD.Example.COM".to_owned());
4291
4292 let mut summaries_for = |query: &str| {
4293 let response = proxy
4294 .query_certificate_for_domain(query.to_owned())
4295 .expect("the domain certificate query must succeed")
4296 .expect("the domain certificate query must return content");
4297 match response.content_type {
4298 Some(ContentType::CertificatesByAddress(list)) => list
4299 .certificates
4300 .iter()
4301 .map(|entry| entry.certificate_summaries.len())
4302 .sum::<usize>(),
4303 _ => panic!("the domain query must answer with CertificatesByAddress"),
4304 }
4305 };
4306
4307 for query in [
4308 "MiXeD.Example.COM",
4309 "mixed.example.com",
4310 "MIXED.EXAMPLE.COM",
4311 ] {
4312 assert_eq!(
4313 summaries_for(query),
4314 1,
4315 "querying {query:?} must find the certificate stored as MiXeD.Example.COM",
4316 );
4317 }
4318
4319 assert_eq!(
4320 summaries_for("other.example.com"),
4321 0,
4322 "normalising the query must not make an unrelated domain match",
4323 );
4324 }
4325
4326 fn https_mux_session_with_peer() -> (HttpsSession, std::net::TcpStream) {
4330 let proxy = proxy_with_certificate_domain("lolcatho.st".to_owned());
4331 let listener = proxy
4332 .listeners
4333 .values()
4334 .next()
4335 .expect("the test proxy has one listener")
4336 .clone();
4337 let pool = Rc::downgrade(&proxy.pool);
4338 let rustls_details = ServerConnection::new(listener.borrow().rustls_details.clone())
4339 .expect("a test rustls server session must be created");
4340 let public_address = *listener.borrow().get_addr();
4341 let proxy = Rc::new(RefCell::new(proxy));
4342
4343 let accepting = std::net::TcpListener::bind("127.0.0.1:0")
4344 .expect("test listener must bind to a loopback port");
4345 let address = accepting
4346 .local_addr()
4347 .expect("test listener must report its local address");
4348 let stream = std::net::TcpStream::connect(address).expect("loopback connect must complete");
4349 let (client, client_address) = accepting.accept().expect("loopback accept must complete");
4350 client
4351 .set_nonblocking(true)
4352 .expect("the client stand-in must be nonblocking");
4353 stream
4354 .set_nonblocking(true)
4355 .expect("mio requires a nonblocking stream");
4356
4357 let mut session = HttpsSession::new(
4358 Duration::from_secs(30),
4359 Duration::from_secs(30),
4360 Duration::from_secs(30),
4361 Duration::from_secs(30),
4362 false,
4363 listener,
4364 pool,
4365 proxy,
4366 public_address,
4367 rustls_details,
4368 MioTcpStream::from_std(stream),
4369 client_address,
4370 Token(0),
4371 Duration::from_secs(0),
4372 );
4373 let handshake = match session.state.take() {
4374 HttpsStateMachine::Handshake(handshake) => handshake,
4375 _ => panic!("a non-expect-proxy session must start in the TLS handshake state"),
4376 };
4377 session.state = session
4378 .upgrade_handshake(handshake)
4379 .expect("the handshake must upgrade to a mux session");
4380 (session, client)
4381 }
4382
4383 fn frontend_shutdowns_of_a_close(
4387 mut session: HttpsSession,
4388 client: &mut std::net::TcpStream,
4389 ) -> (usize, bool) {
4390 use std::io::Read;
4391
4392 let before = mux::shutdown_writes();
4393 session.close();
4394 let shutdowns = mux::shutdown_writes() - before;
4395 drop(session);
4396
4397 let deadline = Instant::now() + Duration::from_secs(2);
4398 let mut chunk = [0u8; 4096];
4399 let saw_fin = loop {
4400 match client.read(&mut chunk) {
4401 Ok(0) => break true,
4402 Ok(_) => {}
4403 Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
4404 std::thread::sleep(Duration::from_millis(5));
4405 }
4406 Err(_) => break false,
4407 }
4408 };
4409 (shutdowns, saw_fin)
4410 }
4411
4412 #[test]
4428 fn https_close_skips_the_frontend_shutdown_once_the_client_closed() {
4429 for how in ["epoll HUP", "read EOF"] {
4430 let (mut session, mut client) = https_mux_session_with_peer();
4431 client
4432 .shutdown(std::net::Shutdown::Write)
4433 .expect("the client stand-in must send its FIN");
4434 let HttpsStateMachine::Mux(mux) = &mut session.state else {
4435 unreachable!("https_mux_session_with_peer returns a mux session")
4436 };
4437 if how == "epoll HUP" {
4438 mux.frontend.readiness_mut().event.insert(Ready::HUP);
4439 } else {
4440 match &mut mux.frontend {
4441 mux::Connection::H1(connection) => connection.socket.peer_disconnected = true,
4442 mux::Connection::H2(connection) => connection.socket.peer_disconnected = true,
4443 }
4444 }
4445
4446 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4447 assert_eq!(
4448 shutdowns, 0,
4449 "a frontend whose client closed ({how}) must not be shut down before its close"
4450 );
4451 assert!(
4452 saw_fin,
4453 "the close alone must still deliver the FIN to a half-closed client ({how})"
4454 );
4455 }
4456 }
4457
4458 #[test]
4461 fn https_close_keeps_the_frontend_shutdown_while_the_client_is_there() {
4462 let (session, mut client) = https_mux_session_with_peer();
4463 let (shutdowns, saw_fin) = frontend_shutdowns_of_a_close(session, &mut client);
4464 assert_eq!(
4465 shutdowns, 1,
4466 "a frontend whose client is still there must keep its shutdown(SHUT_WR)"
4467 );
4468 assert!(saw_fin, "the client must observe the FIN");
4469 }
4470}
4471
4472#[cfg(test)]
4473mod listener_sibling_tests {
4474 use sozu_command::{
4475 config::ListenerBuilder,
4476 proto::command::{
4477 CertificateAndKey, HstsConfig, PathRule, RequestHttpFrontend, SocketAddress,
4478 },
4479 };
4480
4481 use super::*;
4482 use crate::testing::{ServerParts, prebuild_server, provide_port};
4483
4484 fn proxy() -> HttpsProxy {
4485 let ServerParts {
4486 registry,
4487 sessions,
4488 pool,
4489 backends,
4490 ..
4491 } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4492 HttpsProxy::new(registry, sessions, pool, backends)
4493 }
4494
4495 fn add(proxy: &mut HttpsProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
4496 let config = ListenerBuilder::new_https(address)
4497 .with_interface(interface)
4498 .to_tls(None)
4499 .expect("could not build the listener config");
4500 proxy
4501 .add_listener(config, Token(token))
4502 .expect("could not add the listener");
4503 }
4504
4505 fn certificate(address: SocketAddress) -> AddCertificate {
4506 AddCertificate {
4507 address,
4508 certificate: CertificateAndKey {
4509 certificate: include_str!("../assets/certificate.pem").to_owned(),
4510 key: include_str!("../assets/key.pem").to_owned(),
4511 certificate_chain: Vec::new(),
4512 versions: Vec::new(),
4513 names: vec!["example.com".to_owned()],
4514 },
4515 expired_at: None,
4516 }
4517 }
4518
4519 fn front(address: SocketAddress) -> RequestHttpFrontend {
4520 RequestHttpFrontend {
4521 cluster_id: Some("cluster-a".to_owned()),
4522 address,
4523 hostname: "example.com".to_owned(),
4524 path: PathRule::prefix("/".to_owned()),
4525 ..Default::default()
4526 }
4527 }
4528
4529 fn serves_certificate(proxy: &HttpsProxy, token: usize) -> bool {
4530 proxy.listeners[&Token(token)]
4531 .borrow()
4532 .resolver
4533 .0
4534 .lock()
4535 .expect("resolver lock")
4536 .domains
4537 .domain_lookup(b"example.com", true)
4538 .is_some()
4539 }
4540
4541 fn routes(proxy: &HttpsProxy, token: usize) -> bool {
4542 proxy.listeners[&Token(token)]
4543 .borrow()
4544 .fronts
4545 .lookup("example.com", "/", &Method::Get)
4546 .is_ok()
4547 }
4548
4549 #[test]
4555 fn a_listener_added_next_to_a_sibling_takes_over_its_certificates_and_frontends() {
4556 let mut proxy = proxy();
4557 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4558 add(&mut proxy, address, None, 0);
4559 proxy
4560 .add_certificate(certificate(address))
4561 .expect("add the certificate");
4562 proxy
4563 .add_https_frontend(front(address))
4564 .expect("add the frontend");
4565 add(&mut proxy, address, Some("lo"), 1);
4566
4567 assert!(serves_certificate(&proxy, 0));
4568 assert!(
4569 serves_certificate(&proxy, 1),
4570 "the listener added after the certificate must serve it too"
4571 );
4572 assert!(
4573 routes(&proxy, 1),
4574 "the listener added after the frontend must route it too"
4575 );
4576
4577 proxy
4578 .remove_https_frontend(front(address))
4579 .expect("remove the frontend");
4580 assert!(!routes(&proxy, 0));
4581 assert!(!routes(&proxy, 1));
4582 }
4583 fn hsts(max_age: u32) -> HstsConfig {
4584 HstsConfig {
4585 enabled: Some(true),
4586 max_age: Some(max_age),
4587 ..Default::default()
4588 }
4589 }
4590
4591 fn add_with_hsts(
4592 proxy: &mut HttpsProxy,
4593 address: SocketAddress,
4594 interface: Option<&str>,
4595 token: usize,
4596 hsts: Option<HstsConfig>,
4597 ) {
4598 let mut config = ListenerBuilder::new_https(address)
4599 .with_interface(interface)
4600 .to_tls(None)
4601 .expect("could not build the listener config");
4602 config.hsts = hsts;
4603 proxy
4604 .add_listener(config, Token(token))
4605 .expect("could not add the listener");
4606 }
4607
4608 fn tagged_front(address: SocketAddress) -> RequestHttpFrontend {
4611 RequestHttpFrontend {
4612 tags: BTreeMap::from([("owner".to_owned(), "team-a".to_owned())]),
4613 ..front(address)
4614 }
4615 }
4616
4617 fn served_hsts(proxy: &HttpsProxy, token: usize) -> Option<String> {
4620 let route = proxy.listeners[&Token(token)]
4621 .borrow()
4622 .fronts
4623 .lookup("example.com", "/", &Method::Get)
4624 .expect("the listener must route example.com");
4625 route
4626 .headers_response
4627 .iter()
4628 .find(|edit| edit.key.eq_ignore_ascii_case(b"strict-transport-security"))
4629 .map(|edit| String::from_utf8_lossy(&edit.val).into_owned())
4630 }
4631
4632 fn patch_hsts(proxy: &HttpsProxy, token: usize, address: SocketAddress, hsts: HstsConfig) {
4633 proxy.listeners[&Token(token)]
4634 .borrow_mut()
4635 .update_config(&UpdateHttpsListenerConfig {
4636 address,
4637 hsts: Some(hsts),
4638 ..Default::default()
4639 })
4640 .expect("patch the listener HSTS default");
4641 }
4642
4643 #[test]
4656 fn a_rejected_patch_leaves_the_listener_unchanged() {
4657 use sozu_command::proto::command::{AlpnProtocols, UpdateHttpsListenerConfig};
4658
4659 let mut proxy = proxy();
4660 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4661 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4662 proxy
4663 .add_https_frontend(tagged_front(address))
4664 .expect("add the frontend");
4665
4666 let valid_prefix = UpdateHttpsListenerConfig {
4667 address,
4668 front_timeout: Some(99),
4669 sozu_id_header: Some("X-Edge-Id".to_owned()),
4670 alpn_protocols: Some(AlpnProtocols {
4671 values: vec!["http/1.1".to_owned()],
4672 }),
4673 answers: BTreeMap::from([(
4674 "404".to_owned(),
4675 "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n".to_owned(),
4676 )]),
4677 ..Default::default()
4678 };
4679 let rejected = [
4680 UpdateHttpsListenerConfig {
4681 hsts: Some(HstsConfig {
4682 enabled: None,
4683 max_age: Some(200),
4684 ..Default::default()
4685 }),
4686 ..valid_prefix.clone()
4687 },
4688 UpdateHttpsListenerConfig {
4689 answers: BTreeMap::from([(
4690 "503".to_owned(),
4691 "not a valid http response".to_owned(),
4692 )]),
4693 hsts: Some(hsts(200)),
4694 ..valid_prefix.clone()
4695 },
4696 ];
4697 for patch in rejected {
4698 let listener = &proxy.listeners[&Token(0)];
4699 let config_before = listener.borrow().config.clone();
4700 let rustls_before = listener.borrow().rustls_details.clone();
4701 let answers_before = listener.borrow().get_answers().clone();
4702 listener
4703 .borrow_mut()
4704 .update_config(&patch)
4705 .expect_err("the patch must be refused");
4706 let listener = listener.borrow();
4707 assert_eq!(
4708 listener.config, config_before,
4709 "a refused patch must leave the listener configuration unchanged"
4710 );
4711 assert!(
4712 Arc::ptr_eq(&rustls_before, &listener.rustls_details),
4713 "a refused patch must not publish a new rustls context"
4714 );
4715 assert!(
4716 Rc::ptr_eq(&answers_before, listener.get_answers()),
4717 "a refused patch must not publish a new answer registry"
4718 );
4719 drop(listener);
4720 assert_eq!(
4721 served_hsts(&proxy, 0).as_deref(),
4722 Some("max-age=100"),
4723 "a refused patch must not refresh the frontends' HSTS"
4724 );
4725 }
4726
4727 proxy.listeners[&Token(0)]
4728 .borrow_mut()
4729 .update_config(&valid_prefix)
4730 .expect("the valid prefix alone must apply");
4731 let listener = proxy.listeners[&Token(0)].borrow();
4732 assert_eq!(listener.config.front_timeout, 99);
4733 assert_eq!(listener.config.alpn_protocols, vec!["http/1.1".to_owned()]);
4734 }
4735
4736 #[test]
4741 fn a_copied_frontend_without_hsts_inherits_the_new_listener_default() {
4742 let mut proxy = proxy();
4743 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4744 add_with_hsts(&mut proxy, address, None, 0, None);
4745 proxy
4746 .add_https_frontend(tagged_front(address))
4747 .expect("add the frontend");
4748 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(100)));
4749
4750 assert_eq!(served_hsts(&proxy, 0), None);
4751 assert_eq!(
4752 served_hsts(&proxy, 1).as_deref(),
4753 Some("max-age=100"),
4754 "the listener added after the frontend must serve its own HSTS default"
4755 );
4756
4757 patch_hsts(&proxy, 0, address, hsts(200));
4759 patch_hsts(&proxy, 1, address, hsts(300));
4760 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=200"));
4761 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=300"));
4762 }
4763
4764 #[test]
4767 fn a_frontend_with_explicit_hsts_is_never_overridden_by_a_listener_default() {
4768 let mut proxy = proxy();
4769 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4770 add_with_hsts(&mut proxy, address, None, 0, Some(hsts(100)));
4771 proxy
4772 .add_https_frontend(RequestHttpFrontend {
4773 hsts: Some(hsts(42)),
4774 ..tagged_front(address)
4775 })
4776 .expect("add the frontend");
4777 add_with_hsts(&mut proxy, address, Some("lo"), 1, Some(hsts(300)));
4778
4779 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4780 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4781
4782 patch_hsts(&proxy, 0, address, hsts(500));
4783 patch_hsts(&proxy, 1, address, hsts(600));
4784 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=42"));
4785 assert_eq!(served_hsts(&proxy, 1).as_deref(), Some("max-age=42"));
4786 }
4787
4788 #[test]
4792 fn a_listener_hsts_patch_refreshes_every_frontend_without_its_own_hsts() {
4793 for initial in [None, Some(hsts(100))] {
4794 let mut proxy = proxy();
4795 let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4796 add_with_hsts(&mut proxy, address, None, 0, initial);
4797 proxy
4798 .add_https_frontend(tagged_front(address))
4799 .expect("add the frontend");
4800
4801 patch_hsts(&proxy, 0, address, hsts(700));
4802 assert_eq!(served_hsts(&proxy, 0).as_deref(), Some("max-age=700"));
4803
4804 patch_hsts(
4805 &proxy,
4806 0,
4807 address,
4808 HstsConfig {
4809 enabled: Some(false),
4810 ..Default::default()
4811 },
4812 );
4813 assert_eq!(served_hsts(&proxy, 0), None);
4814 }
4815 }
4816
4817 const TEST_CA_PEM: &str = include_str!("../assets/certificate.pem");
4819
4820 fn test_crypto_provider() -> Arc<CryptoProvider> {
4821 Arc::new(default_provider())
4822 }
4823
4824 fn https_config_with_client_auth(
4825 mode: ClientAuthMode,
4826 cas: &[&str],
4827 crls: &[&str],
4828 ) -> HttpsListenerConfig {
4829 let address = SocketAddress::new_v4(127, 0, 0, 1, 1049);
4830 let mut config = ListenerBuilder::new_https(address)
4831 .to_tls(None)
4832 .expect("default HTTPS listener config");
4833 config.client_auth = Some(mode as i32);
4834 config.client_ca_certificates = cas.iter().map(|s| s.to_string()).collect();
4835 config.client_ca_crls = crls.iter().map(|s| s.to_string()).collect();
4836 config
4837 }
4838
4839 #[test]
4840 fn client_cert_verifier_none_is_disabled() {
4841 let provider = test_crypto_provider();
4843 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthNone, &[], &[]);
4844 assert!(
4845 HttpsListener::client_cert_verifier(&config, &provider)
4846 .expect("verifier build")
4847 .is_none()
4848 );
4849
4850 let mut absent = config;
4851 absent.client_auth = None;
4852 assert!(
4853 HttpsListener::client_cert_verifier(&absent, &provider)
4854 .expect("verifier build")
4855 .is_none(),
4856 "absent client_auth must decode to NONE"
4857 );
4858 }
4859
4860 #[test]
4861 fn client_cert_verifier_rejects_unknown_mode() {
4862 let provider = test_crypto_provider();
4865 let mut config =
4866 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4867 config.client_auth = Some(999);
4868 assert!(matches!(
4869 HttpsListener::client_cert_verifier(&config, &provider),
4870 Err(ListenerError::ClientAuth(_))
4871 ));
4872 }
4873
4874 #[test]
4875 fn client_cert_verifier_required_builds_with_ca() {
4876 let provider = test_crypto_provider();
4877 let config =
4878 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[TEST_CA_PEM], &[]);
4879 assert!(
4880 HttpsListener::client_cert_verifier(&config, &provider)
4881 .expect("verifier build")
4882 .is_some()
4883 );
4884 }
4885
4886 #[test]
4887 fn client_cert_verifier_optional_builds_with_ca() {
4888 let provider = test_crypto_provider();
4889 let config =
4890 https_config_with_client_auth(ClientAuthMode::ClientAuthOptional, &[TEST_CA_PEM], &[]);
4891 assert!(
4892 HttpsListener::client_cert_verifier(&config, &provider)
4893 .expect("verifier build")
4894 .is_some()
4895 );
4896 }
4897
4898 #[test]
4899 fn client_cert_verifier_requires_a_trusted_ca() {
4900 let provider = test_crypto_provider();
4903 let config = https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[], &[]);
4904 assert!(matches!(
4905 HttpsListener::client_cert_verifier(&config, &provider),
4906 Err(ListenerError::ClientAuth(_))
4907 ));
4908 }
4909
4910 #[test]
4911 fn client_cert_verifier_rejects_malformed_ca() {
4912 let provider = test_crypto_provider();
4913 let config = https_config_with_client_auth(
4914 ClientAuthMode::ClientAuthRequired,
4915 &["not a pem certificate"],
4916 &[],
4917 );
4918 assert!(matches!(
4920 HttpsListener::client_cert_verifier(&config, &provider),
4921 Err(ListenerError::ClientAuth(_))
4922 ));
4923 }
4924
4925 #[test]
4926 fn client_cert_verifier_rejects_empty_ca_entry_among_valid_ones() {
4927 let provider = test_crypto_provider();
4931 let config = https_config_with_client_auth(
4932 ClientAuthMode::ClientAuthRequired,
4933 &[TEST_CA_PEM, "not a certificate"],
4934 &[],
4935 );
4936 assert!(matches!(
4937 HttpsListener::client_cert_verifier(&config, &provider),
4938 Err(ListenerError::ClientAuth(_))
4939 ));
4940 }
4941
4942 #[test]
4943 fn client_cert_verifier_rejects_empty_crl_entry() {
4944 let provider = test_crypto_provider();
4947 let config = https_config_with_client_auth(
4948 ClientAuthMode::ClientAuthRequired,
4949 &[TEST_CA_PEM],
4950 &["not a crl"],
4951 );
4952 assert!(matches!(
4953 HttpsListener::client_cert_verifier(&config, &provider),
4954 Err(ListenerError::ClientAuth(_))
4955 ));
4956 }
4957
4958 const MTLS_CA_PEM: &str = include_str!("../assets/mtls/ca-cert.pem");
4967 const MTLS_CLIENT_PEM: &str = include_str!("../assets/mtls/client-cert.pem");
4968 const MTLS_CRL_CURRENT: &str = include_str!("../assets/mtls/crl-current.pem");
4970 const MTLS_CRL_REVOKED: &str = include_str!("../assets/mtls/crl-revoked.pem");
4972 const MTLS_CRL_EXPIRED: &str = include_str!("../assets/mtls/crl-expired.pem");
4974 const MTLS_CRL_OTHER_CA: &str = include_str!("../assets/mtls/crl-other-ca.pem");
4976 const MTLS_CRL_PARTITION_A: &str = include_str!("../assets/mtls/crl-partition-a.pem");
4979 const MTLS_CRL_PARTITION_B: &str = include_str!("../assets/mtls/crl-partition-b.pem");
4980
4981 fn verify_mtls_client(crls: &[&str]) -> Result<(), rustls::Error> {
4984 verify_mtls_client_at(crls, rustls::pki_types::UnixTime::now())
4985 }
4986
4987 fn verify_mtls_client_at(
4989 crls: &[&str],
4990 time: rustls::pki_types::UnixTime,
4991 ) -> Result<(), rustls::Error> {
4992 let config =
4993 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
4994 let verifier = HttpsListener::client_cert_verifier(&config, &test_crypto_provider())
4995 .expect("a CA and well-formed CRLs must build a verifier")
4996 .expect("required client auth must install a verifier");
4997 let client = CertificateDer::from_pem_slice(MTLS_CLIENT_PEM.as_bytes())
4998 .expect("the client fixture is a PEM certificate");
4999 verifier.verify_client_cert(&client, &[], time).map(|_| ())
5000 }
5001
5002 fn build_mtls_listener(crls: &[&str]) -> Result<(), String> {
5005 let config =
5006 https_config_with_client_auth(ClientAuthMode::ClientAuthRequired, &[MTLS_CA_PEM], crls);
5007 match HttpsListener::client_cert_verifier(&config, &test_crypto_provider()) {
5008 Ok(_) => Ok(()),
5009 Err(ListenerError::ClientAuth(reason)) => Err(reason),
5010 Err(other) => panic!("unexpected listener error {other:?}"),
5011 }
5012 }
5013
5014 #[test]
5015 fn client_revocation_accepts_a_client_no_crl_revokes() {
5016 assert_eq!(
5017 verify_mtls_client(&[]),
5018 Ok(()),
5019 "precondition: the client chains to the CA"
5020 );
5021 assert_eq!(
5022 verify_mtls_client(&[MTLS_CRL_CURRENT]),
5023 Ok(()),
5024 "a current CRL from the client's issuer that does not list it must not reject it"
5025 );
5026 }
5027
5028 #[test]
5029 fn client_revocation_rejects_a_revoked_client() {
5030 assert_eq!(
5033 verify_mtls_client(&[MTLS_CRL_REVOKED]),
5034 Err(rustls::Error::InvalidCertificate(
5035 rustls::CertificateError::Revoked
5036 ))
5037 );
5038 }
5039
5040 #[test]
5041 fn client_revocation_refuses_an_expired_crl_only_where_the_operator_states_it() {
5042 let now = i64::try_from(
5045 std::time::SystemTime::now()
5046 .duration_since(std::time::UNIX_EPOCH)
5047 .expect("a clock after 1970")
5048 .as_secs(),
5049 )
5050 .expect("a clock within i64");
5051 let refused = HttpsListener::check_crls_current(&[MTLS_CRL_EXPIRED.to_owned()], now);
5052 assert!(
5053 matches!(refused, Err(ListenerError::ClientAuth(ref reason)) if reason.contains("expired")),
5054 "an expired CRL must be refused when stated, got {refused:?}"
5055 );
5056 assert!(
5057 HttpsListener::check_crls_current(
5058 &[MTLS_CRL_CURRENT.to_owned(), MTLS_CRL_OTHER_CA.to_owned()],
5059 now
5060 )
5061 .is_ok()
5062 );
5063 assert_eq!(build_mtls_listener(&[MTLS_CRL_EXPIRED]), Ok(()));
5067 let config = https_config_with_client_auth(
5068 ClientAuthMode::ClientAuthRequired,
5069 &[MTLS_CA_PEM],
5070 &[MTLS_CRL_EXPIRED],
5071 );
5072 assert!(HttpsListener::try_new(config, Token(0)).is_ok());
5073 }
5074
5075 #[test]
5076 fn client_revocation_rejects_a_client_once_its_crl_has_lapsed() {
5077 let june_2125 =
5084 rustls::pki_types::UnixTime::since_unix_epoch(Duration::from_secs(4_905_100_800));
5085 let verdict = verify_mtls_client_at(&[MTLS_CRL_CURRENT], june_2125);
5086 assert!(
5087 matches!(
5088 verdict,
5089 Err(rustls::Error::InvalidCertificate(
5090 rustls::CertificateError::ExpiredRevocationList
5091 | rustls::CertificateError::ExpiredRevocationListContext { .. }
5092 ))
5093 ),
5094 "a CRL past its nextUpdate must reject the client, got {verdict:?}"
5095 );
5096 assert_eq!(verify_mtls_client_at(&[], june_2125), Ok(()));
5099 }
5100
5101 #[test]
5102 fn client_revocation_rejects_a_client_no_configured_crl_covers() {
5103 assert_eq!(
5107 verify_mtls_client(&[MTLS_CRL_OTHER_CA]),
5108 Err(rustls::Error::InvalidCertificate(
5109 rustls::CertificateError::UnknownRevocationStatus
5110 ))
5111 );
5112 assert_eq!(
5114 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_CURRENT]),
5115 Ok(())
5116 );
5117 }
5118
5119 #[test]
5120 fn client_revocation_refuses_two_crls_of_one_issuer_with_overlapping_scopes() {
5121 for crls in [
5125 vec![MTLS_CRL_CURRENT, MTLS_CRL_REVOKED],
5126 vec![MTLS_CRL_REVOKED, MTLS_CRL_CURRENT],
5127 vec![MTLS_CRL_CURRENT, MTLS_CRL_PARTITION_A],
5129 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_CURRENT],
5130 vec![MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_A],
5132 ] {
5133 let refused = build_mtls_listener(&crls);
5134 assert!(
5135 refused
5136 .as_ref()
5137 .is_err_and(|reason| reason.contains("overlapping")),
5138 "overlapping CRLs of one issuer must fail the listener, got {refused:?}"
5139 );
5140 }
5141 assert!(build_mtls_listener(&[&format!("{MTLS_CRL_CURRENT}{MTLS_CRL_REVOKED}")]).is_err());
5143 }
5144
5145 #[test]
5146 fn client_revocation_takes_the_partitions_of_one_issuer() {
5147 assert_eq!(
5150 build_mtls_listener(&[MTLS_CRL_PARTITION_A, MTLS_CRL_PARTITION_B]),
5151 Ok(())
5152 );
5153 }
5154
5155 #[test]
5156 fn client_revocation_takes_one_crl_per_issuer() {
5157 assert_eq!(
5160 verify_mtls_client(&[MTLS_CRL_OTHER_CA, MTLS_CRL_REVOKED]),
5161 Err(rustls::Error::InvalidCertificate(
5162 rustls::CertificateError::Revoked
5163 ))
5164 );
5165 assert_eq!(
5166 verify_mtls_client(&[MTLS_CRL_REVOKED, MTLS_CRL_OTHER_CA]),
5167 Err(rustls::Error::InvalidCertificate(
5168 rustls::CertificateError::Revoked
5169 ))
5170 );
5171 }
5172
5173 #[test]
5179 fn a_client_auth_policy_patch_rebuilds_the_listener_verifier() {
5180 let config = https_config_with_client_auth(
5181 ClientAuthMode::ClientAuthRequired,
5182 &[MTLS_CA_PEM],
5183 &[MTLS_CRL_CURRENT],
5184 );
5185 let mut listener =
5186 HttpsListener::try_new(config.clone(), Token(0)).expect("build the mTLS listener");
5187 let original_context = listener.rustls_details.clone();
5188
5189 let refreshed = HttpsListenerConfig {
5190 client_ca_crls: vec![MTLS_CRL_REVOKED.to_owned()],
5191 ..config.clone()
5192 }
5193 .client_auth_policy();
5194 listener
5195 .update_config(&UpdateHttpsListenerConfig {
5196 address: config.address,
5197 client_auth_policy: Some(refreshed.clone()),
5198 ..Default::default()
5199 })
5200 .expect("a buildable policy must be applied");
5201 assert_eq!(listener.config.client_auth_policy(), refreshed);
5202 assert!(
5203 !Arc::ptr_eq(&listener.rustls_details, &original_context),
5204 "the verifier lives in the rustls context, which must be rebuilt"
5205 );
5206
5207 let applied_context = listener.rustls_details.clone();
5208 let unbuildable = ClientAuthPolicy {
5209 client_auth: Some(ClientAuthMode::ClientAuthRequired as i32),
5210 client_ca_certificates: vec![],
5211 client_ca_crls: vec![],
5212 };
5213 assert!(
5214 listener
5215 .update_config(&UpdateHttpsListenerConfig {
5216 address: config.address,
5217 client_auth_policy: Some(unbuildable.clone()),
5218 ..Default::default()
5219 })
5220 .is_err(),
5221 "required client auth with no trusted CA must be refused"
5222 );
5223 assert!(HttpsListener::validate_client_auth_policy(&unbuildable).is_err());
5224 assert_eq!(
5225 listener.config.client_auth_policy(),
5226 refreshed,
5227 "a refused policy leaves the applied one in place"
5228 );
5229 assert!(Arc::ptr_eq(&listener.rustls_details, &applied_context));
5230 }
5231}