diff --git a/Cargo.lock b/Cargo.lock index 96793c4e6..db81850ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -151,9 +151,9 @@ checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "775a8770d29db3dadcb858482cc240af7b2ffde4ac4de67d1d4955728103f0e2" dependencies = [ "derive_arbitrary", ] @@ -580,9 +580,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.34" +version = "1.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "0f57c4b4da2a9d619dd035f27316d7a426305b75be93d09e92f2b9229c34feaf" dependencies = [ "jobserver", "libc", @@ -1063,6 +1063,7 @@ dependencies = [ "dotenvy", "ethers-core", "humantime", + "hyper-util", "ipnetwork", "jsonwebtoken", "ldap3", @@ -1104,6 +1105,7 @@ dependencies = [ "tonic-build", "tonic-health", "totp-lite", + "tower 0.5.1", "tower-http", "tracing", "tracing-subscriber", @@ -1155,9 +1157,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "d475dfebcb4854d596b17b09f477616f80f17a550517f2b3615d8c205d5c802b" dependencies = [ "proc-macro2", "quote", @@ -1888,9 +1890,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "hashlink" @@ -2131,9 +2133,9 @@ dependencies = [ [[package]] name = "hyper-timeout" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ "hyper 1.5.0", "hyper-util", @@ -2342,24 +2344,23 @@ dependencies = [ [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "idna" -version = "1.0.2" +name = "idna_adapter" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", - "smallvec", - "utf8_iter", ] [[package]] @@ -2434,7 +2435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", "serde", ] @@ -2613,7 +2614,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna 1.0.2", + "idna 1.0.3", "mime", "native-tls", "nom", @@ -4012,9 +4013,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" dependencies = [ "bitflags 2.6.0", "errno", @@ -5022,18 +5023,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", @@ -5610,12 +5611,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 099217e2d..a72140e27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,19 +107,21 @@ webauthn-rs-proto = "0.5" x25519-dalek = { version = "2.0", features = ["static_secrets"] } [dev-dependencies] -bytes = "1.6" +bytes = "1.8" claims = "0.7" +hyper-util = "0.1" matches = "0.1" -regex = "1.10" +regex = "1.11" reqwest = { version = "0.11", features = [ - "json", "cookies", - "stream", + "json", "multipart", "rustls-tls", + "stream", ], default-features = false } serde_qs = "0.13" webauthn-authenticator-rs = { version = "0.5", features = ["softpasskey"] } +tower = { version = "0.5", features = ["util"] } [build-dependencies] prost-build = "0.13" diff --git a/src/grpc/gateway.rs b/src/grpc/gateway.rs index b53997ef7..66ab8bdd1 100644 --- a/src/grpc/gateway.rs +++ b/src/grpc/gateway.rs @@ -33,6 +33,9 @@ use crate::{ mail::Mail, }; +#[cfg(test)] +mod tests; + tonic::include_proto!("gateway"); fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { @@ -85,8 +88,7 @@ impl GatewayHandler { events_tx: Sender, mail_tx: UnboundedSender, ) -> Result { - let endpoint = Endpoint::from_shared(gateway.url.to_string())?; - let endpoint = endpoint + let endpoint = Endpoint::from_shared(gateway.url.to_string())? .http2_keep_alive_interval(TEN_SECS) .tcp_keepalive(Some(TEN_SECS)) .keep_alive_while_idle(true); @@ -207,11 +209,23 @@ impl GatewayHandler { }; } + /// Connect to Gateway and handle its messages through gRPC. pub(super) async fn handle_connection(&mut self) -> ! { let uri = self.endpoint.uri(); loop { - info!("Connecting to gateway {uri}"); - let mut client = gateway_client::GatewayClient::new(self.endpoint.connect_lazy()); + #[cfg(not(test))] + let channel = self.endpoint.connect_lazy(); + #[cfg(test)] + let channel = self.endpoint.connect_with_connector_lazy(tower::service_fn( + |_: tonic::transport::Uri| async { + Ok::<_, std::io::Error>(hyper_util::rt::TokioIo::new( + tokio::net::UnixStream::connect(tests::TONIC_SOCKET).await?, + )) + }, + )); + + debug!("Connecting to gateway {uri}"); + let mut client = gateway_client::GatewayClient::new(channel); let (tx, rx) = mpsc::unbounded_channel(); let response = match client.bidi(UnboundedReceiverStream::new(rx)).await { Ok(response) => response, @@ -317,7 +331,6 @@ impl GatewayHandler { self.gateway.network_id, ); // Get device by public key and fill in stats.device_id - // FIXME: keep an in-memory device map to avoid repeated DB requests match Device::find_by_pubkey(&self.pool, &public_key).await { Ok(Some(device)) => { stats.device_id = device.id; diff --git a/src/grpc/gateway/tests.rs b/src/grpc/gateway/tests.rs new file mode 100644 index 000000000..04e376d68 --- /dev/null +++ b/src/grpc/gateway/tests.rs @@ -0,0 +1,38 @@ +use std::net::{IpAddr, Ipv4Addr}; + +use ipnetwork::IpNetwork; +use tokio::sync::{broadcast, mpsc::unbounded_channel}; + +use super::*; + +pub(super) static TONIC_SOCKET: &str = "tonic.sock"; + +#[sqlx::test] +async fn test_gateway(pool: PgPool) { + let network = WireguardNetwork::new( + "TestNet".to_string(), + IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1)), 24).unwrap(), + 50051, + "0.0.0.0".to_string(), + None, + vec![IpNetwork::new(IpAddr::V4(Ipv4Addr::new(10, 1, 1, 0)), 24).unwrap()], + false, + 0, + 0, + ) + .save(&pool) + .await + .unwrap(); + let gateway = Gateway::new(network.id, "http://[::]:50051") + .save(&pool) + .await + .unwrap(); + let (events_tx, _events_rx) = broadcast::channel::(16); + let (mail_tx, _mail_rx) = unbounded_channel::(); + + let mut gateway_handler = GatewayHandler::new(gateway, None, pool, events_tx, mail_tx).unwrap(); + let handle = tokio::spawn(async move { + gateway_handler.handle_connection().await; + }); + handle.abort(); +} diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs index c17d467a1..8630fc23f 100644 --- a/src/grpc/mod.rs +++ b/src/grpc/mod.rs @@ -127,7 +127,7 @@ pub async fn run_grpc_gateway_stream( // Helper closure to launch `GatewayHandler`. let mut launch_gateway_handler = |gateway: Gateway| -> Result { - let mut gateway_client = GatewayHandler::new( + let mut gateway_handler = GatewayHandler::new( gateway, tls_config.clone(), pool.clone(), @@ -135,7 +135,7 @@ pub async fn run_grpc_gateway_stream( mail_tx.clone(), )?; let abort_handle = tasks.spawn(async move { - gateway_client.handle_connection().await; + gateway_handler.handle_connection().await; }); Ok(abort_handle) }; @@ -223,8 +223,7 @@ pub async fn run_grpc_bidi_stream( let mut client_mfa_server = ClientMfaServer::new(pool.clone(), mail_tx, events_tx); let polling_server = PollingServer::new(pool); - let endpoint = Endpoint::from_shared(config.proxy_url.as_deref().unwrap())?; - let endpoint = endpoint + let endpoint = Endpoint::from_shared(config.proxy_url.as_deref().unwrap())? .http2_keep_alive_interval(TEN_SECS) .tcp_keepalive(Some(TEN_SECS)) .keep_alive_while_idle(true); @@ -254,7 +253,7 @@ pub async fn run_grpc_bidi_stream( } Ok(Some(received)) => { info!("Received message from proxy."); - debug!("Received the following message from proxy: {received:?}"); + debug!("Message from proxy: {received:?}"); let payload = match received.payload { // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) Some(core_request::Payload::EnrollmentStart(request)) => { diff --git a/src/handlers/mail.rs b/src/handlers/mail.rs index b44177448..0e4d2724e 100644 --- a/src/handlers/mail.rs +++ b/src/handlers/mail.rs @@ -64,7 +64,7 @@ fn internal_error(to: &str, subject: &str, error: &impl Display) -> ApiResponse } } -pub async fn test_mail( +pub(crate) async fn test_mail( _admin: AdminRole, session: SessionInfo, State(appstate): State, @@ -121,7 +121,7 @@ async fn read_logs() -> String { } } -pub async fn send_support_data( +pub(crate) async fn send_support_data( _admin: AdminRole, session: SessionInfo, State(appstate): State, @@ -176,7 +176,7 @@ pub async fn send_support_data( } } -pub fn send_new_device_added_email( +pub(crate) fn send_new_device_added_email( device_name: &str, public_key: &str, template_locations: &[TemplateLocation], @@ -215,7 +215,7 @@ pub fn send_new_device_added_email( } } -pub async fn send_gateway_disconnected_email( +pub(crate) async fn send_gateway_disconnected_email( gateway_name: Option, network_name: &str, gateway_url: &str, @@ -253,7 +253,7 @@ pub async fn send_gateway_disconnected_email( Ok(()) } -pub async fn send_new_device_login_email( +pub(crate) async fn send_new_device_login_email( user_email: &str, mail_tx: &UnboundedSender, session: &Session, @@ -283,7 +283,7 @@ pub async fn send_new_device_login_email( Ok(()) } -pub async fn send_new_device_ocid_login_email( +pub(crate) async fn send_new_device_ocid_login_email( user_email: &str, oauth2client_name: String, mail_tx: &UnboundedSender, @@ -315,7 +315,7 @@ pub async fn send_new_device_ocid_login_email( Ok(()) } -pub fn send_mfa_configured_email( +pub(crate) fn send_mfa_configured_email( session: Option<&Session>, user: &User, mfa_method: &MFAMethod, @@ -347,7 +347,7 @@ pub fn send_mfa_configured_email( } } -pub fn send_email_mfa_activation_email( +pub(crate) fn send_email_mfa_activation_email( user: &User, mail_tx: &UnboundedSender, session: &Session, @@ -382,7 +382,7 @@ pub fn send_email_mfa_activation_email( } } -pub fn send_email_mfa_code_email( +pub(crate) fn send_email_mfa_code_email( user: &User, mail_tx: &UnboundedSender, session: Option<&Session>, @@ -417,7 +417,7 @@ pub fn send_email_mfa_code_email( } } -pub fn send_password_reset_email( +pub(crate) fn send_password_reset_email( user: &User, mail_tx: &UnboundedSender, service_url: Url, @@ -449,7 +449,7 @@ pub fn send_password_reset_email( } } -pub fn send_password_reset_success_email( +pub(crate) fn send_password_reset_success_email( user: &User, mail_tx: &UnboundedSender, ip_address: Option<&str>, diff --git a/src/mail.rs b/src/mail.rs index 00f19f8a8..a4cf959f0 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -40,12 +40,12 @@ pub enum MailError { /// Subset of Settings object representing SMTP configuration struct SmtpSettings { - pub server: String, - pub port: u16, - pub encryption: SmtpEncryption, - pub user: String, - pub password: String, - pub sender: String, + server: String, + port: u16, + encryption: SmtpEncryption, + user: String, + password: String, + sender: String, } impl SmtpSettings {