mod tests { use super::*; #[test] fn http2_connection_limits_default_and_validate_independently() { let defaults: Http2Connections = serde_json::from_str("{}").unwrap(); assert_eq!(defaults.idle_timeout_seconds, 300); assert_eq!(defaults.max_connection_lifetime_seconds, 0); assert_eq!(defaults.drain_timeout_seconds, 10); defaults.validate().unwrap(); let configured = Http2Connections { idle_timeout_seconds: 45, max_connection_lifetime_seconds: 3_600, drain_timeout_seconds: 20, }; configured.validate().unwrap(); assert_eq!(configured.idle_timeout(), Duration::from_secs(45)); assert_eq!(configured.max_connection_lifetime(), Some(Duration::from_secs(3_600))); assert_eq!(configured.drain_timeout(), Duration::from_secs(20)); assert!(Http2Connections { idle_timeout_seconds: 0, ..defaults }.validate().is_err()); assert!(Http2Connections { max_connection_lifetime_seconds: 2_592_001, ..defaults }.validate().is_err()); assert!(Http2Connections { drain_timeout_seconds: 0, ..defaults }.validate().is_err()); } #[test] fn new_install_server_header_defaults_to_rustproxy_without_changing_legacy_missing_field() { let fresh = crate::config::Config::default(); assert!(matches!(fresh.server_header.mode, ServerMode::Custom)); assert_eq!(fresh.server_header.value, "RustProxy"); let legacy: crate::config::Config = serde_json::from_str("{}").unwrap(); assert!(matches!(legacy.server_header.mode, ServerMode::Preserve)); } #[test] fn server_policy_and_injection() { let mut headers = HeaderMap::new(); headers.insert("server", "origin".parse().unwrap()); ServerHeader { mode: ServerMode::Hide, ..Default::default() } .apply(&mut headers); assert!(!headers.contains_key("server")); let custom = ServerHeader { mode: ServerMode::Custom, value: "RustProxy/v0.1.0".into(), }; custom.validate().unwrap(); custom.apply(&mut headers); assert_eq!(headers["server"], "RustProxy/v0.1.0"); assert!(ServerHeader { mode: ServerMode::Custom, value: "bad\r\nx: y".into() } .validate() .is_err()); } #[test] fn explicit_startup_binds_are_injected_and_cannot_be_removed() { let locks = StartupLocks { http: Some("127.0.0.1:18080".parse().unwrap()), https: Some("127.0.0.1:18443".parse().unwrap()), administration: Some("127.0.0.1:19090".parse().unwrap()), }; let mut config = crate::config::Config::default(); config.administration.listen = Some("127.0.0.1:19999".parse().unwrap()); locks.ensure_config(&mut config); assert!(config.administration.listen.is_none()); locks .validate(&config, "127.0.0.1:19090".parse().unwrap()) .unwrap(); let http = config .listeners .endpoints .iter_mut() .find(|endpoint| { endpoint.bindings().iter().any(|binding| { binding.address == "127.0.0.1:18080".parse().unwrap() && !binding.tls }) }) .unwrap(); http.enabled = false; locks.ensure_config(&mut config); assert!(config.listeners.endpoints.iter().any(|endpoint| { endpoint.enabled && endpoint.bindings().iter().any(|binding| { binding.address == "127.0.0.1:18080".parse().unwrap() && !binding.tls }) })); config.listeners.endpoints.retain(|endpoint| { !endpoint.bindings().iter().any(|binding| { binding.address == "127.0.0.1:18080".parse().unwrap() && !binding.tls }) }); assert!(locks .validate(&config, "127.0.0.1:19090".parse().unwrap()) .is_err()); } #[test] fn explicit_startup_bind_replaces_canonical_saved_binding() { let locks = StartupLocks { http: Some("0.0.0.0:8181".parse().unwrap()), https: Some("0.0.0.0:8443".parse().unwrap()), administration: None, }; let mut config = crate::config::Config::default(); config.listeners = Listeners::from_startup( "0.0.0.0:80".parse().unwrap(), Some("0.0.0.0:443".parse().unwrap()), ); config.listeners.endpoints[0] .additional_addresses .push("127.0.0.1:80".parse().unwrap()); config.listeners.endpoints[1] .additional_addresses .push("127.0.0.1:443".parse().unwrap()); locks.ensure_config(&mut config); let http = config.listeners.endpoints.iter().find(|item| item.id == "http").unwrap(); let https = config.listeners.endpoints.iter().find(|item| item.id == "https").unwrap(); assert_eq!(http.binding.address, "0.0.0.0:8181".parse().unwrap()); assert!(http.additional_addresses.is_empty()); assert_eq!(https.binding.address, "0.0.0.0:8443".parse().unwrap()); assert!(https.additional_addresses.is_empty()); assert!(!config.listeners.endpoints.iter().filter(|item| item.enabled).flat_map(|item| item.bindings()).any(|binding| binding.address.port() == 80 || binding.address.port() == 443)); } #[test] fn startup_defaults_become_named_listeners() { let saved = Listeners::from_startup( "0.0.0.0:80".parse().unwrap(), Some("0.0.0.0:443".parse().unwrap()), ); assert_eq!(saved.endpoints.len(), 2); assert_eq!(saved.endpoints[0].id, "http"); assert_eq!(saved.endpoints[0].binding.address.port(), 80); assert_eq!(saved.endpoints[1].id, "https"); assert_eq!(saved.endpoints[1].binding.address.port(), 443); assert_eq!( saved.endpoints[1].binding.http2_settings, Some(Http2Connections::default()) ); assert!(saved.endpoints[1].binding.http3.is_some()); } #[test] fn named_listener_can_bind_multiple_explicit_addresses() { let endpoint = NamedListener { id: "web".into(), name: "Web".into(), scope: ListenerScope::Public, enabled: true, binding: crate::vhost::Listener { address: "192.0.2.10:443".parse().unwrap(), tls: true, http2: true, http2_settings: Some(Http2Connections::default()), h2c: false, http3: None, }, additional_addresses: vec!["192.0.2.11:443".parse().unwrap()], }; let bindings = endpoint.bindings(); assert_eq!(bindings.len(), 2); assert_eq!(bindings[0].address, "192.0.2.10:443".parse().unwrap()); assert_eq!(bindings[1].address, "192.0.2.11:443".parse().unwrap()); assert_eq!(bindings[0].tls, bindings[1].tls); assert_eq!(bindings[0].http2, bindings[1].http2); } #[test] fn multi_address_listener_rejects_unspecified_or_duplicate_addresses() { let policy = crate::config::ValidationPolicy { tls_addr: None, cert_dir: None, data_dir: None, proxy_addr: "127.0.0.1:18080".parse().unwrap(), admin_addr: "127.0.0.1:19090".parse().unwrap(), }; let mut listeners = Listeners { endpoints: vec![NamedListener { id: "web".into(), name: "Web".into(), scope: ListenerScope::Public, enabled: true, binding: crate::vhost::Listener { address: "0.0.0.0:8080".parse().unwrap(), tls: false, http2: false, http2_settings: None, h2c: false, http3: None, }, additional_addresses: vec!["192.0.2.10:8080".parse().unwrap()], }], }; assert!(listeners.validate(&policy).is_err()); listeners.endpoints[0].binding.address = "192.0.2.10:8080".parse().unwrap(); assert!(listeners.validate(&policy).is_err()); listeners.endpoints[0].additional_addresses = vec!["192.0.2.11:8080".parse().unwrap()]; assert!(listeners.validate(&policy).is_ok()); } #[test] fn administration_certificate_requires_tls_and_known_inventory_id() { let inventory = crate::tls::Settings { certificates: vec![crate::tls::Certificate { issuer: None, id: "panel".into(), covered_hosts: Vec::new(), hosts: vec![], cert_file: "panel.pem".into(), key_file: "panel.key".into(), }], ..crate::tls::Settings::default() }; let mut admin = Administration { tls: false, certificate_id: Some("panel".into()), ..Administration::default() }; assert!(admin.validate(&inventory).is_err()); admin.tls = true; assert!(admin.validate(&inventory).is_ok()); admin.certificate_id = Some("missing".into()); assert!(admin.validate(&inventory).is_err()); } #[test] fn administration_session_lifetime_is_7_to_30_days_and_hot_apply_only() { let tls = crate::tls::Settings::default(); let mut admin = Administration::default(); assert_eq!(admin.session_days, 30); assert_eq!(admin.session_seconds(), 30 * 24 * 60 * 60); assert!(admin.validate(&tls).is_ok()); let active = admin.clone(); admin.session_days = 7; assert!(admin.validate(&tls).is_ok()); assert!(!admin.restart_differs(&active)); admin.session_days = 6; assert!(admin.validate(&tls).is_err()); admin.session_days = 31; assert!(admin.validate(&tls).is_err()); } #[test] fn administration_hostnames_are_normalized_and_unique() { let mut admin = Administration { tls: true, allowed_hosts: vec!["Panel.Example.COM.".into()], ..Administration::default() }; admin.validate(&crate::tls::Settings::default()).unwrap(); assert_eq!(admin.allowed_hosts, vec!["panel.example.com".to_owned()]); admin.allowed_hosts = vec!["panel.example.com".into(), "PANEL.EXAMPLE.COM.".into()]; assert!(admin.validate(&crate::tls::Settings::default()).is_err()); } #[test] fn administration_hostname_count_is_not_artificially_capped() { let mut admin = Administration { tls: true, allowed_hosts: (0..40) .map(|index| format!("admin-{index}.example.test")) .collect(), ..Administration::default() }; assert!(admin.validate(&crate::tls::Settings::default()).is_ok()); assert_eq!(admin.allowed_hosts.len(), 40); } }