diff --git a/Cargo.lock b/Cargo.lock index f76eaa6c050..f858e61b759 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -704,6 +704,7 @@ dependencies = [ "tedge_config", "tedge_config_macros", "tedge_http_ext", + "tedge_utils", "tokio", "tokio-tungstenite", "tracing", @@ -756,6 +757,7 @@ dependencies = [ "tedge_actors", "tedge_config", "tedge_http_ext", + "tedge_test_utils", "tedge_utils", "thiserror", "time", @@ -4058,6 +4060,7 @@ dependencies = [ "serde_json", "tedge_actors", "tedge_config", + "tedge_test_utils", "thiserror", "tokio", ] diff --git a/clippy.toml b/clippy.toml index 8182a08dc27..6658f218eca 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ disallowed-types = ["reqwest::ClientBuilder"] -disallowed-methods = ["reqwest::Client::builder"] +disallowed-methods = ["reqwest::Client::builder", "reqwest::Client::new", "hyper::client::Client::new"] diff --git a/crates/common/axum_tls/src/acceptor.rs b/crates/common/axum_tls/src/acceptor.rs index 47283c085a7..9ad99eff496 100644 --- a/crates/common/axum_tls/src/acceptor.rs +++ b/crates/common/axum_tls/src/acceptor.rs @@ -107,6 +107,7 @@ fn common_name<'a>(cert: Option<&'a (&[u8], X509Certificate)>) -> Option<&'a str } #[cfg(test)] +#[allow(clippy::disallowed_methods)] mod tests { use super::*; use crate::ssl_config; diff --git a/crates/common/axum_tls/src/files.rs b/crates/common/axum_tls/src/files.rs index bfcca9cb8ea..f327551343f 100644 --- a/crates/common/axum_tls/src/files.rs +++ b/crates/common/axum_tls/src/files.rs @@ -321,10 +321,7 @@ mod tests { let app = Router::new().route("/test", get(|| async { "it works!" })); let task = tokio::spawn(crate::start_tls_server(listener, config, app)); - let client = reqwest::Client::builder() - .add_root_certificate(cert) - .build() - .unwrap(); + let client = client_builder().add_root_certificate(cert).build().unwrap(); assert_eq!( client .get(format!("https://localhost:{port}/test")) @@ -339,6 +336,11 @@ mod tests { task.abort(); } + #[allow(clippy::disallowed_methods, clippy::disallowed_types)] + fn client_builder() -> reqwest::ClientBuilder { + reqwest::Client::builder() + } + fn listener() -> (u16, std::net::TcpListener) { let mut port = 3500; loop { diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index e9c14808e48..1e402c68d77 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -278,6 +278,7 @@ pub struct ExternalPluginCommand { } impl ExternalPluginCommand { + #[allow(clippy::too_many_arguments)] pub fn new( name: impl Into, path: impl Into, diff --git a/crates/core/tedge_agent/src/file_transfer_server/actor.rs b/crates/core/tedge_agent/src/file_transfer_server/actor.rs index 5e68a2af4af..51a2ec3c400 100644 --- a/crates/core/tedge_agent/src/file_transfer_server/actor.rs +++ b/crates/core/tedge_agent/src/file_transfer_server/actor.rs @@ -242,6 +242,7 @@ mod tests { format!("http://localhost:{}/tedge/file-transfer/{path}", self.port) } + #[allow(clippy::disallowed_methods)] fn client(&self) -> reqwest::Client { reqwest::Client::new() } @@ -304,6 +305,7 @@ mod tests { .context("building anonymous client") } + #[allow(clippy::disallowed_types, clippy::disallowed_methods)] fn client_builder(&self) -> anyhow::Result { let reqwest_certificate = Certificate::from_der( &self diff --git a/crates/extensions/c8y_auth_proxy/Cargo.toml b/crates/extensions/c8y_auth_proxy/Cargo.toml index 34c40a2d89a..d85b3503102 100644 --- a/crates/extensions/c8y_auth_proxy/Cargo.toml +++ b/crates/extensions/c8y_auth_proxy/Cargo.toml @@ -23,6 +23,7 @@ rustls = { workspace = true } tedge_actors = { workspace = true } tedge_config = { workspace = true } tedge_config_macros = { workspace = true } +tedge_utils = { workspace = true } tokio = { workspace = true, features = [ "macros", "rt-multi-thread", diff --git a/crates/extensions/c8y_auth_proxy/src/actor.rs b/crates/extensions/c8y_auth_proxy/src/actor.rs index 935462ffaed..20720df20d7 100644 --- a/crates/extensions/c8y_auth_proxy/src/actor.rs +++ b/crates/extensions/c8y_auth_proxy/src/actor.rs @@ -41,10 +41,12 @@ impl C8yAuthProxyBuilder { config: &TEdgeConfig, jwt: &mut ServerActorBuilder, ) -> anyhow::Result { + let reqwest_client = config.root_cert_client().builder().build().unwrap(); let app_data = AppData { is_https: true, host: config.c8y.http.or_config_not_set()?.to_string(), token_manager: TokenManager::new(JwtRetriever::new(jwt)).shared(), + client: reqwest_client, }; let bind = &config.c8y.proxy.bind; let (signal_sender, signal_receiver) = mpsc::channel(10); diff --git a/crates/extensions/c8y_auth_proxy/src/server.rs b/crates/extensions/c8y_auth_proxy/src/server.rs index 4c49e38f66f..24d907bbf7e 100644 --- a/crates/extensions/c8y_auth_proxy/src/server.rs +++ b/crates/extensions/c8y_auth_proxy/src/server.rs @@ -133,11 +133,13 @@ pub(crate) struct AppData { pub is_https: bool, pub host: String, pub token_manager: SharedTokenManager, + pub client: reqwest::Client, } #[derive(Clone)] struct AppState { target_host: TargetHost, + client: reqwest::Client, token_manager: SharedTokenManager, } @@ -156,6 +158,7 @@ impl From for AppState { without_scheme: host.into(), }, token_manager: value.token_manager, + client: value.client, } } } @@ -172,6 +175,12 @@ impl FromRef for SharedTokenManager { } } +impl FromRef for reqwest::Client { + fn from_ref(input: &AppState) -> Self { + input.client.clone() + } +} + #[derive(Clone)] struct TargetHost { http: Arc, @@ -372,6 +381,7 @@ where #[allow(clippy::too_many_arguments)] async fn respond_to( State(host): State, + State(client): State, retrieve_token: State, path: Option>, uri: hyper::Uri, @@ -409,7 +419,6 @@ async fn respond_to( let path = path.to_owned(); return Ok(ws.on_upgrade(|socket| proxy_ws(socket, host, retrieve_token, headers, path))); } - let client = reqwest::Client::new(); let (body, body_clone) = small_body.try_clone(); if body_clone.is_none() { let destination = format!("{}/tenant/currentTenant", host.http); @@ -549,11 +558,7 @@ mod tests { let proxy_port = start_server_port(target.port(), vec!["unused token"]); tokio::spawn(async move { - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - client + reqwest_client() .get(format!("http://127.0.0.1:{proxy_port}/c8y/test")) .send() .await @@ -825,11 +830,7 @@ mod tests { let port = start_server(&server, vec!["test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!("https://localhost:{port}/c8y/hello")) .send() .await @@ -853,6 +854,7 @@ mod tests { let port = start_server_with_certificate(&server, vec!["test-token"], certificate, None); + #[allow(clippy::disallowed_methods)] let client = reqwest::Client::builder() .add_root_certificate(reqwest::tls::Certificate::from_der(&cert_der).unwrap()) .build() @@ -876,11 +878,7 @@ mod tests { let port = start_server(&server, vec!["test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!("https://localhost:{port}/c8y/not-a-known-url")) .send() .await @@ -899,11 +897,7 @@ mod tests { None, ); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!("https://localhost:{port}/c8y/not-a-known-url")) .send() .await @@ -923,11 +917,7 @@ mod tests { let port = start_server(&server, vec!["test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!( "https://localhost:{port}/c8y/inventory/managedObjects?pageSize=100" )) @@ -949,11 +939,7 @@ mod tests { let port = start_server(&server, vec!["test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!( "https://localhost:{port}/c8y/inventory/managedObjects" )) @@ -983,12 +969,8 @@ mod tests { let port = start_server(&server, vec!["old-token", "test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); let body = "A body"; - let res = client + let res = reqwest_client() .put(format!("https://localhost:{port}/c8y/hello")) .header("Content-Length", body.bytes().len()) .body(body) @@ -1019,12 +1001,8 @@ mod tests { let port = start_server(&server, vec!["old-token", "test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); let body = "A body"; - let res = client + let res = reqwest_client() .put(format!("https://localhost:{port}/c8y/hello")) .body(reqwest::Body::wrap_stream(once(ready(Ok::< _, @@ -1058,11 +1036,7 @@ mod tests { let port = start_server(&server, vec!["stale-token", "test-token"]); - let client = reqwest::Client::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(); - let res = client + let res = reqwest_client() .get(format!("https://localhost:{port}/c8y/hello")) .send() .await @@ -1071,6 +1045,14 @@ mod tests { assert_eq!(res.bytes().await.unwrap(), Bytes::from("Succeeded")); } + #[allow(clippy::disallowed_methods)] + fn reqwest_client() -> reqwest::Client { + reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .build() + .unwrap() + } + fn start_server(server: &mockito::Server, tokens: Vec>>) -> u16 { start_server_with_certificate( server, @@ -1100,6 +1082,7 @@ mod tests { start_proxy_to_url(host, tokens, certificate, ca_dir) } + #[allow(clippy::disallowed_methods)] fn start_proxy_to_url( target_host: &str, tokens: Vec>>, @@ -1113,6 +1096,7 @@ mod tests { is_https: false, host: target_host.into(), token_manager: TokenManager::new(JwtRetriever::new(&mut retriever)).shared(), + client: reqwest::Client::new(), }; let trust_store = ca_dir .as_ref() diff --git a/crates/extensions/c8y_http_proxy/Cargo.toml b/crates/extensions/c8y_http_proxy/Cargo.toml index 15ad26ce155..f1f4405795f 100644 --- a/crates/extensions/c8y_http_proxy/Cargo.toml +++ b/crates/extensions/c8y_http_proxy/Cargo.toml @@ -34,6 +34,7 @@ serde = { workspace = true } serde_json = { workspace = true } tedge_actors = { workspace = true, features = ["test-helpers"] } tedge_http_ext = { workspace = true, features = ["test_helpers"] } +tedge_test_utils = { workspace = true } time = { workspace = true } [lints] diff --git a/crates/extensions/c8y_http_proxy/src/tests.rs b/crates/extensions/c8y_http_proxy/src/tests.rs index 439ea3ec22d..288b12d5698 100644 --- a/crates/extensions/c8y_http_proxy/src/tests.rs +++ b/crates/extensions/c8y_http_proxy/src/tests.rs @@ -22,11 +22,13 @@ use tedge_actors::Sender; use tedge_actors::Server; use tedge_actors::ServerActor; use tedge_actors::ServerMessageBoxBuilder; +use tedge_config::TEdgeConfigLocation; use tedge_http_ext::test_helpers::HttpResponseBuilder; use tedge_http_ext::HttpActor; use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpRequestBuilder; use tedge_http_ext::HttpResult; +use tedge_test_utils::fs::TempTedgeDir; use tedge_utils::certificates::RootCertClient; use time::macros::datetime; @@ -352,7 +354,10 @@ async fn retry_internal_id_on_expired_jwt_with_mock() { let target_url = server.url(); let mut jwt = ServerMessageBoxBuilder::new("JWT Actor", 16); - let mut http_actor = HttpActor::new().builder(); + let ttd = TempTedgeDir::new(); + let config_loc = TEdgeConfigLocation::from_custom_root(ttd.path()); + let tedge_config = config_loc.load().unwrap(); + let mut http_actor = HttpActor::new(&tedge_config).builder(); let config = C8YHttpConfig { c8y_http_host: target_url.clone(), @@ -418,7 +423,10 @@ async fn retry_create_event_on_expired_jwt_with_mock() { let target_url = server.url(); let mut jwt = ServerMessageBoxBuilder::new("JWT Actor", 16); - let mut http_actor = HttpActor::new().builder(); + let ttd = TempTedgeDir::new(); + let config_loc = TEdgeConfigLocation::from_custom_root(ttd.path()); + let tedge_config = config_loc.load().unwrap(); + let mut http_actor = HttpActor::new(&tedge_config).builder(); let config = C8YHttpConfig { c8y_http_host: target_url.clone(), diff --git a/crates/extensions/tedge_http_ext/Cargo.toml b/crates/extensions/tedge_http_ext/Cargo.toml index 3d7bca539c8..a83e2d47881 100644 --- a/crates/extensions/tedge_http_ext/Cargo.toml +++ b/crates/extensions/tedge_http_ext/Cargo.toml @@ -38,6 +38,7 @@ tokio = { workspace = true, default_features = false, features = [ [dev-dependencies] mockito = { workspace = true } +tedge_test_utils = { workspace = true } [lints] workspace = true diff --git a/crates/extensions/tedge_http_ext/src/tests.rs b/crates/extensions/tedge_http_ext/src/tests.rs index cd08c4f304c..d6137c41c12 100644 --- a/crates/extensions/tedge_http_ext/src/tests.rs +++ b/crates/extensions/tedge_http_ext/src/tests.rs @@ -1,5 +1,7 @@ use crate::*; use tedge_actors::ClientMessageBox; +use tedge_config::TEdgeConfigLocation; +use tedge_test_utils::fs::TempTedgeDir; #[tokio::test] async fn get_over_https() { @@ -18,7 +20,10 @@ async fn get_over_https() { } async fn spawn_http_actor() -> ClientMessageBox { - let mut builder = HttpActor::new().builder(); + let ttd = TempTedgeDir::new(); + let config_loc = TEdgeConfigLocation::from_custom_root(ttd.path()); + let config = config_loc.load().unwrap(); + let mut builder = HttpActor::new(&config).builder(); let handle = ClientMessageBox::new(&mut builder); tokio::spawn(builder.run()); diff --git a/plugins/c8y_remote_access_plugin/src/lib.rs b/plugins/c8y_remote_access_plugin/src/lib.rs index 4cd471066a0..a6105df879b 100644 --- a/plugins/c8y_remote_access_plugin/src/lib.rs +++ b/plugins/c8y_remote_access_plugin/src/lib.rs @@ -165,8 +165,10 @@ async fn proxy(command: RemoteAccessConnect, config: TEdgeConfig) -> miette::Res let jwt = Jwt::retrieve(&config) .await .context("Failed when requesting JWT from Cumulocity")?; + let client_config = config.cloud_client_tls_config(); - let proxy = WebsocketSocketProxy::connect(&url, command.target_address(), jwt).await?; + let proxy = + WebsocketSocketProxy::connect(&url, command.target_address(), jwt, client_config).await?; proxy.run().await; Ok(()) diff --git a/plugins/c8y_remote_access_plugin/src/proxy.rs b/plugins/c8y_remote_access_plugin/src/proxy.rs index 1260f7245ea..25e6f02ac76 100644 --- a/plugins/c8y_remote_access_plugin/src/proxy.rs +++ b/plugins/c8y_remote_access_plugin/src/proxy.rs @@ -9,6 +9,8 @@ use miette::Context; use miette::Diagnostic; use miette::IntoDiagnostic; use rand::RngCore; +use rustls::ClientConfig; +use std::sync::Arc; use thiserror::Error; use tokio::net::TcpStream; use tokio::net::ToSocketAddrs; @@ -34,9 +36,10 @@ impl WebsocketSocketProxy { url: &Url, socket: SA, jwt: Jwt, + config: ClientConfig, ) -> miette::Result { let socket_future = TcpStream::connect(socket); - let websocket_future = Websocket::new(url, jwt.authorization_header()); + let websocket_future = Websocket::new(url, jwt.authorization_header(), config); match join(socket_future, websocket_future).await { (Err(socket_error), _) => Err(SocketError(socket_error))?, @@ -77,7 +80,7 @@ fn generate_sec_websocket_key() -> String { } impl Websocket { - async fn new(url: &Url, authorization: String) -> miette::Result { + async fn new(url: &Url, authorization: String, config: ClientConfig) -> miette::Result { let request = http::Request::builder() .header("Authorization", authorization) .header("Sec-WebSocket-Key", generate_sec_websocket_key()) @@ -90,12 +93,15 @@ impl Websocket { .into_diagnostic() .context("Instantiating Websocket connection")?; - let socket = async_tungstenite::tokio::connect_async(request) - .await - .into_diagnostic() - .with_context(|| format!("host {url}")) - .context("Connecting to Websocket")? - .0; + let socket = async_tungstenite::tokio::connect_async_with_tls_connector( + request, + Some(Arc::new(config).into()), + ) + .await + .into_diagnostic() + .with_context(|| format!("host {url}")) + .context("Connecting to Websocket")? + .0; Ok(Websocket { socket: WsStream::new(socket),