From f36ed3e2f5df7f05ac965708e85d28214ab63db3 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 19 Jun 2024 12:48:14 +0100 Subject: [PATCH 01/17] Parse root certificate from either file or directory in tedge cert upload Signed-off-by: James Rhodes --- Cargo.lock | 3 ++ crates/common/tedge_utils/Cargo.toml | 5 +- crates/common/tedge_utils/src/certificates.rs | 47 +++++++++++++++++++ crates/common/tedge_utils/src/lib.rs | 1 + .../core/tedge/src/cli/certificate/error.rs | 3 ++ .../core/tedge/src/cli/certificate/upload.rs | 34 ++++++-------- 6 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 crates/common/tedge_utils/src/certificates.rs diff --git a/Cargo.lock b/Cargo.lock index 6c0dd139d8c..cb682d62753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4185,6 +4185,7 @@ version = "1.1.1" dependencies = [ "anyhow", "assert_matches", + "camino", "doku", "futures", "maplit", @@ -4193,6 +4194,8 @@ dependencies = [ "notify", "notify-debouncer-full", "once_cell", + "reqwest", + "rustls-pemfile 1.0.4", "serde", "serde_json", "strum", diff --git a/crates/common/tedge_utils/Cargo.toml b/crates/common/tedge_utils/Cargo.toml index c30fade79ad..b3115f19f0c 100644 --- a/crates/common/tedge_utils/Cargo.toml +++ b/crates/common/tedge_utils/Cargo.toml @@ -17,13 +17,16 @@ fs-notify = ["strum", "notify", "notify-debouncer-full"] timestamp = ["strum", "time", "serde", "serde_json"] [dependencies] -anyhow = "1.0.71" +anyhow = { workspace = true } +camino = { workspace = true } doku = { workspace = true } futures = { workspace = true } mqtt_channel = { workspace = true } nix = { workspace = true } notify = { workspace = true, optional = true } notify-debouncer-full = { workspace = true, optional = true } +reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } +rustls-pemfile = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } strum = { workspace = true, optional = true, features = ["derive"] } diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/tedge_utils/src/certificates.rs new file mode 100644 index 00000000000..0b967155a85 --- /dev/null +++ b/crates/common/tedge_utils/src/certificates.rs @@ -0,0 +1,47 @@ +use anyhow::Context; +use camino::Utf8Path; +use camino::Utf8PathBuf; +use reqwest::Certificate; +use std::fs::File; + +/// Read a directory into a [RootCertStore] +pub fn read_trust_store(ca_dir_or_file: &Utf8Path) -> anyhow::Result> { + let mut certs = Vec::new(); + for path in iter_file_or_directory(ca_dir_or_file) { + let path = + path.with_context(|| format!("reading metadata for file at {ca_dir_or_file}"))?; + + if path.is_dir() { + continue; + } + + let Ok(mut pem_file) = File::open(&path).map(std::io::BufReader::new) else { + continue; + }; + let ders = rustls_pemfile::certs(&mut pem_file) + .with_context(|| format!("reading {path}"))? + .into_iter() + .map(|der| Certificate::from_der(&der).unwrap()); + certs.extend(ders) + } + + Ok(certs) +} + +fn iter_file_or_directory( + possible_dir: &Utf8Path, +) -> Box> + 'static> { + let path = possible_dir.to_path_buf(); + if let Ok(dir) = possible_dir.read_dir_utf8() { + Box::new(dir.map(move |file| match file { + Ok(file) => { + let mut path = path.clone(); + path.push(file.file_name()); + Ok(path) + } + Err(e) => Err(e).with_context(|| format!("reading metadata for file in {path}")), + })) + } else { + Box::new([Ok(path)].into_iter()) + } +} diff --git a/crates/common/tedge_utils/src/lib.rs b/crates/common/tedge_utils/src/lib.rs index a3e4acd1513..d895fcb8350 100644 --- a/crates/common/tedge_utils/src/lib.rs +++ b/crates/common/tedge_utils/src/lib.rs @@ -9,5 +9,6 @@ pub mod futures; #[cfg(feature = "fs-notify")] pub mod notify; +pub mod certificates; #[cfg(feature = "timestamp")] pub mod timestamp; diff --git a/crates/core/tedge/src/cli/certificate/error.rs b/crates/core/tedge/src/cli/certificate/error.rs index 50cb63d113d..9b007026ccc 100644 --- a/crates/core/tedge/src/cli/certificate/error.rs +++ b/crates/core/tedge/src/cli/certificate/error.rs @@ -47,6 +47,9 @@ pub enum CertError { #[error("I/O error")] IoError(#[from] std::io::Error), + #[error(transparent)] + Anyhow(#[from] anyhow::Error), + #[error("Invalid device.cert_path path: {0}")] CertPathError(PathsError), diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs index 293776a2151..10bdd5b8b3a 100644 --- a/crates/core/tedge/src/cli/certificate/upload.rs +++ b/crates/core/tedge/src/cli/certificate/upload.rs @@ -5,6 +5,7 @@ use camino::Utf8PathBuf; use reqwest::StatusCode; use reqwest::Url; use std::io::prelude::*; +use std::io::ErrorKind; use std::path::Path; use tedge_config::HostPort; use tedge_config::TEdgeConfig; @@ -71,28 +72,21 @@ impl UploadCertCmd { let config = TEdgeConfig::try_new(TEdgeConfigLocation::default())?; let root_cert = &config.c8y.root_cert_path; - let client_builder = reqwest::blocking::Client::builder(); - let res = match std::fs::metadata(root_cert) { - Ok(res) => res, - Err(e) => match e.kind() { - std::io::ErrorKind::NotFound => { - return Err(CertError::RootCertificatePathDoesNotExist( - root_cert.to_string(), - )) + let mut client_builder = reqwest::blocking::Client::builder(); + if let Err(e) = std::fs::metadata(root_cert) { + let e = match e.kind() { + ErrorKind::NotFound => { + CertError::RootCertificatePathDoesNotExist(root_cert.to_string()) } - e => return Err(CertError::IoError(e.into())), - }, - }; - - let client = match res.is_file() { - true => { - let cert = std::fs::read(root_cert); + _ => CertError::IoError(e), + }; + return Err(e); + } - let cert_pem = reqwest::Certificate::from_pem(&cert?)?; - client_builder.add_root_certificate(cert_pem).build()? - } - false => client_builder.build()?, - }; + for certificate in tedge_utils::certificates::read_trust_store(root_cert)? { + client_builder = client_builder.add_root_certificate(certificate); + } + let client = client_builder.build()?; // To post certificate c8y requires one of the following endpoints: // https://.cumulocity.url.io[:port]/tenant/tenants//trusted-certificates From de1919b8681188807557605020aa28fee1606f83 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 19 Jun 2024 16:24:48 +0100 Subject: [PATCH 02/17] Respect config-dir in `tedge cert upload` Signed-off-by: James Rhodes --- crates/core/tedge/src/cli/certificate/cli.rs | 1 + crates/core/tedge/src/cli/certificate/upload.rs | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/core/tedge/src/cli/certificate/cli.rs b/crates/core/tedge/src/cli/certificate/cli.rs index 9a33603878b..e7778ed4d6b 100644 --- a/crates/core/tedge/src/cli/certificate/cli.rs +++ b/crates/core/tedge/src/cli/certificate/cli.rs @@ -102,6 +102,7 @@ impl BuildCommand for TEdgeCertCli { device_id: config.device.id.try_read(&config)?.clone(), path: config.device.cert_path.clone(), host: config.c8y.http.or_err()?.to_owned(), + root_cert_path: config.c8y.root_cert_path.clone(), username, password, }, diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs index 10bdd5b8b3a..2dfee44e67e 100644 --- a/crates/core/tedge/src/cli/certificate/upload.rs +++ b/crates/core/tedge/src/cli/certificate/upload.rs @@ -8,9 +8,8 @@ use std::io::prelude::*; use std::io::ErrorKind; use std::path::Path; use tedge_config::HostPort; -use tedge_config::TEdgeConfig; -use tedge_config::TEdgeConfigLocation; use tedge_config::HTTPS_PORT; +use tedge_utils::certificates::read_trust_store; #[derive(Debug, serde::Deserialize)] struct CumulocityResponse { @@ -31,6 +30,7 @@ pub struct UploadCertCmd { pub path: Utf8PathBuf, pub host: HostPort, pub username: String, + pub root_cert_path: Utf8PathBuf, pub password: String, } @@ -70,20 +70,18 @@ impl UploadCertCmd { self.password.clone() }; - let config = TEdgeConfig::try_new(TEdgeConfigLocation::default())?; - let root_cert = &config.c8y.root_cert_path; let mut client_builder = reqwest::blocking::Client::builder(); - if let Err(e) = std::fs::metadata(root_cert) { + if let Err(e) = std::fs::metadata(&self.root_cert_path) { let e = match e.kind() { ErrorKind::NotFound => { - CertError::RootCertificatePathDoesNotExist(root_cert.to_string()) + CertError::RootCertificatePathDoesNotExist(self.root_cert_path.to_string()) } _ => CertError::IoError(e), }; return Err(e); } - for certificate in tedge_utils::certificates::read_trust_store(root_cert)? { + for certificate in read_trust_store(&self.root_cert_path)? { client_builder = client_builder.add_root_certificate(certificate); } let client = client_builder.build()?; From a7acdb41ddd04f8a98fb7c3e81467ff18d4fd407 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Thu, 18 Jul 2024 20:00:18 +0100 Subject: [PATCH 03/17] Create a mechanism for deriving root certificates from tedge_config Signed-off-by: James Rhodes --- Cargo.lock | 1 + clippy.toml | 2 + crates/common/clippy.toml | 8 ++++ crates/common/download/src/download.rs | 33 +++++++++++---- .../src/tedge_config_cli/tedge_config.rs | 41 +++++++++++++++++++ crates/common/tedge_utils/src/certificates.rs | 26 ++++++++++++ crates/core/plugin_sm/Cargo.toml | 1 + crates/core/plugin_sm/src/plugin.rs | 16 +++++++- crates/core/plugin_sm/src/plugin_manager.rs | 1 + crates/core/tedge_agent/src/agent.rs | 6 ++- crates/core/tedge_mapper/src/c8y/mapper.rs | 3 +- crates/extensions/c8y_auth_proxy/Cargo.toml | 2 +- crates/extensions/c8y_http_proxy/src/actor.rs | 1 + crates/extensions/c8y_http_proxy/src/lib.rs | 6 ++- .../tedge_downloader_ext/src/actor.rs | 24 +++++++++-- plugins/c8y_firmware_plugin/src/lib.rs | 3 +- 16 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 clippy.toml create mode 100644 crates/common/clippy.toml diff --git a/Cargo.lock b/Cargo.lock index cb682d62753..102ea934a7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2643,6 +2643,7 @@ dependencies = [ "serial_test", "tedge_api", "tedge_config", + "tedge_utils", "tempfile", "test-case", "thiserror", diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..8182a08dc27 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +disallowed-types = ["reqwest::ClientBuilder"] +disallowed-methods = ["reqwest::Client::builder"] diff --git a/crates/common/clippy.toml b/crates/common/clippy.toml new file mode 100644 index 00000000000..5206fb3abbf --- /dev/null +++ b/crates/common/clippy.toml @@ -0,0 +1,8 @@ +disallowed-types = [ + # "reqwest::ClientBuilder", + # "reqwest::Client", +] + +disallowed-methods = [ + "reqwest::Client::builder" +] \ No newline at end of file diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 4ad253f38a8..c5ca7089367 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -10,6 +10,7 @@ use log::warn; use nix::sys::statvfs; pub use partial_response::InvalidResponseError; use reqwest::header; +use reqwest::Client; use reqwest::Identity; use serde::Deserialize; use serde::Serialize; @@ -26,6 +27,7 @@ use std::time::Duration; use tedge_utils::file::move_file; use tedge_utils::file::FileError; use tedge_utils::file::PermissionEntry; +use tedge_utils::certificates::RootCertClient; #[cfg(target_os = "linux")] use nix::fcntl::fallocate; @@ -106,18 +108,28 @@ pub struct Downloader { target_filename: PathBuf, target_permission: PermissionEntry, backoff: ExponentialBackoff, - identity: Option, + client: Client, } impl Downloader { /// Creates a new downloader which downloads to a target directory and uses /// default permissions. - pub fn new(target_path: PathBuf, identity: Option) -> Self { + pub fn new( + target_path: PathBuf, + identity: Option, + root_cert_client: RootCertClient, + ) -> Self { + let mut client_builder = root_cert_client.builder(); + if let Some(identity) = identity { + client_builder = client_builder.identity(identity); + } + // TODO don't unwrap + let client = client_builder.build().unwrap(); Self { target_filename: target_path, target_permission: PermissionEntry::default(), backoff: default_backoff(), - identity, + client, } } @@ -127,12 +139,19 @@ impl Downloader { target_path: PathBuf, target_permission: PermissionEntry, identity: Option, + root_cert_client: RootCertClient, ) -> Self { + let mut client_builder = root_cert_client.builder(); + if let Some(identity) = identity { + client_builder = client_builder.identity(identity); + } + // TODO no unwrap + let client = client_builder.build().unwrap(); Self { target_filename: target_path, target_permission, backoff: default_backoff(), - identity, + client, } } @@ -389,11 +408,7 @@ impl Downloader { let backoff = self.backoff.clone(); let operation = || async { - let mut client = reqwest::Client::builder(); - if let Some(identity) = &self.identity { - client = client.identity(identity.clone()); - } - let mut request = client.build()?.get(url.url()); + let mut request = self.client.get(url.url()); if let Some(Auth::Bearer(token)) = &url.auth { request = request.bearer_auth(token) } diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index 6ebab60e74e..a90129cc761 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -20,6 +20,7 @@ use certificate::PemCertificate; use doku::Document; use doku::Type; use once_cell::sync::Lazy; +use reqwest::Certificate; use serde::Deserialize; use std::borrow::Cow; use std::fmt; @@ -32,13 +33,17 @@ use std::ops::Deref; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use std::sync::OnceLock; use tedge_config_macros::all_or_nothing; use tedge_config_macros::define_tedge_config; use tedge_config_macros::struct_field_aliases; use tedge_config_macros::struct_field_paths; pub use tedge_config_macros::ConfigNotSet; use tedge_config_macros::OptionalConfig; +use tedge_utils::certificates::read_trust_store; +use tedge_utils::certificates::RootCertClient; use toml::Table; +use tracing::error; const DEFAULT_ROOT_CERT_PATH: &str = "/etc/ssl/certs"; @@ -958,7 +963,43 @@ define_tedge_config! { #[tedge_config(default(value = true), example = "true", example = "false")] enable: bool, }, +} +static CLOUD_ROOT_CERTIFICATES: OnceLock> = OnceLock::new(); + +impl TEdgeConfigReader { + pub fn root_cert_client(&self) -> RootCertClient { + let roots = CLOUD_ROOT_CERTIFICATES.get_or_init(|| { + let c8y_roots = read_trust_store(&self.c8y.root_cert_path).unwrap_or_else(move |e| { + error!( + "Unable to read certificates from {}: {e}", + ReadableKey::C8yRootCertPath + ); + vec![] + }); + let az_roots = read_trust_store(&self.az.root_cert_path).unwrap_or_else(move |e| { + error!( + "Unable to read certificates from {}: {e}", + ReadableKey::AzRootCertPath + ); + vec![] + }); + let aws_roots = read_trust_store(&self.aws.root_cert_path).unwrap_or_else(move |e| { + error!( + "Unable to read certificates from {}: {e}", + ReadableKey::AwsRootCertPath + ); + vec![] + }); + c8y_roots + .into_iter() + .chain(az_roots) + .chain(aws_roots) + .collect() + }); + + RootCertClient::from(roots.clone()) + } } fn c8y_topic_prefix() -> TopicPrefix { diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/tedge_utils/src/certificates.rs index 0b967155a85..781d03b30bd 100644 --- a/crates/common/tedge_utils/src/certificates.rs +++ b/crates/common/tedge_utils/src/certificates.rs @@ -2,7 +2,33 @@ use anyhow::Context; use camino::Utf8Path; use camino::Utf8PathBuf; use reqwest::Certificate; +use reqwest::ClientBuilder; use std::fs::File; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct RootCertClient { + certificates: Arc<[Certificate]>, +} + +impl RootCertClient { + pub fn builder(&self) -> ClientBuilder { + self.certificates + .iter() + .cloned() + .fold(ClientBuilder::new(), |builder, cert| { + builder.add_root_certificate(cert) + }) + } +} + +impl From> for RootCertClient { + fn from(certificates: Arc<[Certificate]>) -> Self { + Self { + certificates + } + } +} /// Read a directory into a [RootCertStore] pub fn read_trust_store(ca_dir_or_file: &Utf8Path) -> anyhow::Result> { diff --git a/crates/core/plugin_sm/Cargo.toml b/crates/core/plugin_sm/Cargo.toml index b9a9be841a5..4755d8e8b2d 100644 --- a/crates/core/plugin_sm/Cargo.toml +++ b/crates/core/plugin_sm/Cargo.toml @@ -20,6 +20,7 @@ serde = { workspace = true } serde_json = { workspace = true } tedge_api = { workspace = true } tedge_config = { workspace = true } +tedge_utils = { workspace = true } thiserror = { workspace = true } time = { workspace = true, features = ["formatting"] } tokio = { workspace = true, features = ["process", "rt"] } diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index a474ffa2691..e9c82baddf0 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use tedge_utils::certificates::RootCertClient; use csv::ReaderBuilder; use download::Downloader; use regex::Regex; @@ -72,6 +73,7 @@ pub trait Plugin { command_log, download_path, self.identity(), + self.root_cert_client().clone(), ) .await? } @@ -85,6 +87,7 @@ pub trait Plugin { } fn identity(&self) -> Option<&Identity>; + fn root_cert_client(&self) -> &RootCertClient; async fn apply_all( &self, @@ -115,6 +118,7 @@ pub trait Plugin { command_log.as_deref_mut(), download_path, self.identity(), + self.root_cert_client().clone(), ) .await { @@ -168,6 +172,7 @@ pub trait Plugin { mut command_log: Option<&mut CommandLog>, download_path: &Path, identity: Option<&Identity>, + root_cert_client: RootCertClient, ) -> Result<(), SoftwareError> { let downloader = Self::download_from_url( module, @@ -175,6 +180,7 @@ pub trait Plugin { command_log.as_deref_mut(), download_path, identity, + root_cert_client, ) .await?; let result = self.install(module, command_log.as_deref_mut()).await; @@ -189,9 +195,10 @@ pub trait Plugin { mut command_log: Option<&mut CommandLog>, download_path: &Path, identity: Option<&Identity>, + root_cert_client: RootCertClient, ) -> Result { let sm_path = sm_path(&module.name, &module.version, download_path); - let downloader = Downloader::new(sm_path, identity.map(|id| id.to_owned())); + let downloader = Downloader::new(sm_path, identity.map(|id| id.to_owned()), root_cert_client); if let Some(ref mut logger) = command_log { logger @@ -266,6 +273,7 @@ pub struct ExternalPluginCommand { exclude: Option, include: Option, identity: Option, + root_cert_client: RootCertClient, } impl ExternalPluginCommand { @@ -277,6 +285,7 @@ impl ExternalPluginCommand { exclude: Option, include: Option, identity: Option, + root_cert_client: RootCertClient, ) -> ExternalPluginCommand { ExternalPluginCommand { name: name.into(), @@ -286,6 +295,7 @@ impl ExternalPluginCommand { exclude, include, identity, + root_cert_client, } } @@ -571,6 +581,10 @@ impl Plugin for ExternalPluginCommand { fn identity(&self) -> Option<&Identity> { self.identity.as_ref() } + + fn root_cert_client(&self) -> &RootCertClient { + &self.root_cert_client + } } pub fn deserialize_module_info( diff --git a/crates/core/plugin_sm/src/plugin_manager.rs b/crates/core/plugin_sm/src/plugin_manager.rs index 326df65818c..7d91da077b2 100644 --- a/crates/core/plugin_sm/src/plugin_manager.rs +++ b/crates/core/plugin_sm/src/plugin_manager.rs @@ -210,6 +210,7 @@ impl ExternalPlugins { config.software.plugin.exclude.or_none().cloned(), config.software.plugin.include.or_none().cloned(), identity, + config.root_cert_client(), ); self.plugin_map.insert(plugin_name.into(), plugin); } diff --git a/crates/core/tedge_agent/src/agent.rs b/crates/core/tedge_agent/src/agent.rs index 5a192835883..ec856fd79e9 100644 --- a/crates/core/tedge_agent/src/agent.rs +++ b/crates/core/tedge_agent/src/agent.rs @@ -52,6 +52,7 @@ use tedge_script_ext::ScriptActor; use tedge_signal_ext::SignalActor; use tedge_uploader_ext::UploaderActor; use tedge_utils::file::create_directory_with_defaults; +use tedge_utils::certificates::RootCertClient; use tracing::info; use tracing::instrument; use tracing::warn; @@ -78,6 +79,7 @@ pub(crate) struct AgentConfig { pub tedge_http_host: Arc, pub service: TEdgeConfigReaderService, pub identity: Option, + pub root_cert_client: RootCertClient, pub fts_url: Arc, pub is_sudo_enabled: bool, pub capabilities: Capabilities, @@ -149,6 +151,7 @@ impl AgentConfig { let operations_dir = config_dir.join("operations"); let identity = tedge_config.http.client.auth.identity()?; + let root_cert_client = tedge_config.root_cert_client(); let is_sudo_enabled = tedge_config.sudo.enable; @@ -181,6 +184,7 @@ impl AgentConfig { mqtt_device_topic_id, tedge_http_host, identity, + root_cert_client, fts_url, is_sudo_enabled, service: tedge_config.service.clone(), @@ -278,7 +282,7 @@ impl Agent { let mut fs_watch_actor_builder = FsWatchActorBuilder::new(); let mut downloader_actor_builder = - DownloaderActor::new(self.config.identity.clone()).builder(); + DownloaderActor::new(self.config.identity.clone(), self.config.root_cert_client.clone()).builder(); let mut uploader_actor_builder = UploaderActor::new(self.config.identity).builder(); // Instantiate config manager actor if config_snapshot or both operations are enabled diff --git a/crates/core/tedge_mapper/src/c8y/mapper.rs b/crates/core/tedge_mapper/src/c8y/mapper.rs index 1331c28436d..f50f2b4ffdc 100644 --- a/crates/core/tedge_mapper/src/c8y/mapper.rs +++ b/crates/core/tedge_mapper/src/c8y/mapper.rs @@ -201,8 +201,9 @@ impl TEdgeComponent for CumulocityMapper { let mut timer_actor = TimerActor::builder(); let identity = tedge_config.http.client.auth.identity()?; + let root_cert_client = tedge_config.root_cert_client(); let mut uploader_actor = UploaderActor::new(identity.clone()).builder(); - let mut downloader_actor = DownloaderActor::new(identity).builder(); + let mut downloader_actor = DownloaderActor::new(identity, root_cert_client).builder(); // MQTT client dedicated to monitor the c8y-bridge client status and also // set service down status on shutdown, using a last-will message. diff --git a/crates/extensions/c8y_auth_proxy/Cargo.toml b/crates/extensions/c8y_auth_proxy/Cargo.toml index 59a215640cb..34c40a2d89a 100644 --- a/crates/extensions/c8y_auth_proxy/Cargo.toml +++ b/crates/extensions/c8y_auth_proxy/Cargo.toml @@ -38,7 +38,7 @@ env_logger = { workspace = true } httparse = { workspace = true } mockito = { workspace = true } rcgen = { workspace = true } -rustls = { workspace = true, features = ["dangerous_configuration"] } +rustls = { workspace = true } tedge_http_ext = { workspace = true, features = ["test_helpers"] } [lints] diff --git a/crates/extensions/c8y_http_proxy/src/actor.rs b/crates/extensions/c8y_http_proxy/src/actor.rs index 2fc156844d6..637612208e7 100644 --- a/crates/extensions/c8y_http_proxy/src/actor.rs +++ b/crates/extensions/c8y_http_proxy/src/actor.rs @@ -517,6 +517,7 @@ impl C8YHttpProxyActor { request.file_path, request.file_permissions, self.config.identity.clone(), + self.config.root_cert_client.clone(), ); downloader.download(&download_info).await?; diff --git a/crates/extensions/c8y_http_proxy/src/lib.rs b/crates/extensions/c8y_http_proxy/src/lib.rs index 953541a3d43..4a12cecba1c 100644 --- a/crates/extensions/c8y_http_proxy/src/lib.rs +++ b/crates/extensions/c8y_http_proxy/src/lib.rs @@ -22,6 +22,7 @@ use tedge_config::ReadError; use tedge_config::TEdgeConfig; use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpResult; +use tedge_utils::certificates::RootCertClient; mod actor; pub mod credentials; @@ -32,13 +33,14 @@ pub mod messages; mod tests; /// Configuration of C8Y REST API -#[derive(Default, Clone)] +#[derive(Clone)] pub struct C8YHttpConfig { pub c8y_http_host: String, pub c8y_mqtt_host: String, pub device_id: String, pub tmp_dir: PathBuf, identity: Option, + root_cert_client: RootCertClient, retry_interval: Duration, } @@ -51,6 +53,7 @@ impl TryFrom<&TEdgeConfig> for C8YHttpConfig { let device_id = tedge_config.device.id.try_read(tedge_config)?.to_string(); let tmp_dir = tedge_config.tmp.path.as_std_path().to_path_buf(); let identity = tedge_config.http.client.auth.identity()?; + let root_cert_client = tedge_config.root_cert_client(); let retry_interval = Duration::from_secs(5); Ok(Self { @@ -59,6 +62,7 @@ impl TryFrom<&TEdgeConfig> for C8YHttpConfig { device_id, tmp_dir, identity, + root_cert_client, retry_interval, }) } diff --git a/crates/extensions/tedge_downloader_ext/src/actor.rs b/crates/extensions/tedge_downloader_ext/src/actor.rs index 7d0be137c1d..117ab9470b5 100644 --- a/crates/extensions/tedge_downloader_ext/src/actor.rs +++ b/crates/extensions/tedge_downloader_ext/src/actor.rs @@ -14,6 +14,7 @@ use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; use tedge_utils::file::PermissionEntry; +use tedge_utils::certificates::RootCertClient; #[derive(Debug, Clone, Eq, PartialEq)] pub struct DownloadRequest { @@ -70,6 +71,7 @@ pub struct DownloaderActor { config: ServerConfig, key: std::marker::PhantomData, identity: Option, + root_cert_client: RootCertClient, } impl Clone for DownloaderActor { @@ -78,16 +80,21 @@ impl Clone for DownloaderActor { config: self.config, key: self.key, identity: self.identity.clone(), + root_cert_client: self.root_cert_client.clone(), } } } impl DownloaderActor { - pub fn new(identity: Option) -> Self { + pub fn new( + identity: Option, + root_cert_client: RootCertClient, + ) -> Self { DownloaderActor { config: <_>::default(), key: PhantomData, identity, + root_cert_client, } } @@ -95,11 +102,17 @@ impl DownloaderActor { ServerActorBuilder::new(self.clone(), &ServerConfig::new(), Sequential) } - pub fn with_capacity(self, capacity: usize, identity: Option) -> Self { + pub fn with_capacity( + self, + capacity: usize, + identity: Option, + root_cert_client: RootCertClient, + ) -> Self { Self { config: self.config.with_capacity(capacity), key: self.key, identity, + root_cert_client, } } } @@ -127,9 +140,14 @@ impl Server for DownloaderActor { request.file_path.clone(), permission, self.identity.clone(), + self.root_cert_client.clone(), ) } else { - Downloader::new(request.file_path.clone(), self.identity.clone()) + Downloader::new( + request.file_path.clone(), + self.identity.clone(), + self.root_cert_client.clone(), + ) }; info!( diff --git a/plugins/c8y_firmware_plugin/src/lib.rs b/plugins/c8y_firmware_plugin/src/lib.rs index 822f61aa318..67e4330747f 100644 --- a/plugins/c8y_firmware_plugin/src/lib.rs +++ b/plugins/c8y_firmware_plugin/src/lib.rs @@ -85,7 +85,8 @@ async fn run_with(tedge_config: TEdgeConfig) -> Result<(), anyhow::Error> { tedge_config.c8y.bridge.topic_prefix.clone(), ); let identity = tedge_config.http.client.auth.identity()?; - let mut downloader_actor = DownloaderActor::new(identity).builder(); + let root_cert_client = tedge_config.root_cert_client(); + let mut downloader_actor = DownloaderActor::new(identity, root_cert_client).builder(); let mut mqtt_actor = MqttActorBuilder::new(mqtt_config.clone().with_session_name(PLUGIN_NAME)); //Instantiate health monitor actor From c5393d99cc3849ba6dfde16d076abf8335ec6cc5 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Thu, 18 Jul 2024 20:13:33 +0100 Subject: [PATCH 04/17] Use RootCertClient in uploader Signed-off-by: James Rhodes --- .gitignore | 3 +++ Cargo.lock | 2 ++ crates/common/download/src/download.rs | 2 +- crates/common/tedge_utils/src/certificates.rs | 4 +--- crates/common/upload/Cargo.toml | 1 + crates/common/upload/src/upload.rs | 11 +++++++++-- crates/core/plugin_sm/src/plugin.rs | 5 +++-- crates/core/tedge_agent/src/agent.rs | 12 ++++++++---- crates/core/tedge_mapper/src/c8y/mapper.rs | 3 ++- crates/extensions/tedge_downloader_ext/src/actor.rs | 7 ++----- crates/extensions/tedge_uploader_ext/Cargo.toml | 1 + crates/extensions/tedge_uploader_ext/src/actor.rs | 13 ++++++++++--- 12 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index c3157dc8c2b..41204d6908b 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ tedge-private-key.pem # temporary changelog _CHANGELOG.md + +# +mutants.out* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 102ea934a7c..61b279ea66d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4176,6 +4176,7 @@ dependencies = [ "reqwest", "tedge_actors", "tedge_test_utils", + "tedge_utils", "tokio", "upload", ] @@ -4763,6 +4764,7 @@ dependencies = [ "mockito", "reqwest", "tedge_test_utils", + "tedge_utils", "tempfile", "thiserror", "tokio", diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index c5ca7089367..4b779f74f16 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -24,10 +24,10 @@ use std::os::unix::prelude::AsRawFd; use std::path::Path; use std::path::PathBuf; use std::time::Duration; +use tedge_utils::certificates::RootCertClient; use tedge_utils::file::move_file; use tedge_utils::file::FileError; use tedge_utils::file::PermissionEntry; -use tedge_utils::certificates::RootCertClient; #[cfg(target_os = "linux")] use nix::fcntl::fallocate; diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/tedge_utils/src/certificates.rs index 781d03b30bd..bd89dfcb0f1 100644 --- a/crates/common/tedge_utils/src/certificates.rs +++ b/crates/common/tedge_utils/src/certificates.rs @@ -24,9 +24,7 @@ impl RootCertClient { impl From> for RootCertClient { fn from(certificates: Arc<[Certificate]>) -> Self { - Self { - certificates - } + Self { certificates } } } diff --git a/crates/common/upload/Cargo.toml b/crates/common/upload/Cargo.toml index e9638434c6b..e027068bea0 100644 --- a/crates/common/upload/Cargo.toml +++ b/crates/common/upload/Cargo.toml @@ -21,6 +21,7 @@ reqwest = { workspace = true, features = [ "rustls-tls-native-roots", "multipart", ] } +tedge_utils = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } tokio-util = { workspace = true, features = ["codec"] } diff --git a/crates/common/upload/src/upload.rs b/crates/common/upload/src/upload.rs index 441e17c438c..cc8b5ddda7d 100644 --- a/crates/common/upload/src/upload.rs +++ b/crates/common/upload/src/upload.rs @@ -13,6 +13,7 @@ use reqwest::multipart; use reqwest::Body; use reqwest::Identity; use std::time::Duration; +use tedge_utils::certificates::RootCertClient; use tokio::fs::File; use tokio_util::codec::BytesCodec; use tokio_util::codec::FramedRead; @@ -132,14 +133,20 @@ pub struct Uploader { source_filename: Utf8PathBuf, backoff: ExponentialBackoff, identity: Option, + root_cert_client: RootCertClient, } impl Uploader { - pub fn new(target_path: Utf8PathBuf, identity: Option) -> Self { + pub fn new( + target_path: Utf8PathBuf, + identity: Option, + root_cert_client: RootCertClient, + ) -> Self { Self { source_filename: target_path, backoff: default_backoff(), identity, + root_cert_client, } } @@ -174,7 +181,7 @@ impl Uploader { let file_body = Body::wrap_stream(FramedRead::new(file, BytesCodec::new())); - let mut client = reqwest::Client::builder(); + let mut client = self.root_cert_client.builder(); if let Some(identity) = self.identity.clone() { client = client.identity(identity); } diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index e9c82baddf0..e9c14808e48 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use tedge_utils::certificates::RootCertClient; use csv::ReaderBuilder; use download::Downloader; use regex::Regex; @@ -18,6 +17,7 @@ use tedge_api::SoftwareModuleUpdate; use tedge_api::SoftwareType; use tedge_api::DEFAULT; use tedge_config::SudoCommandBuilder; +use tedge_utils::certificates::RootCertClient; use tokio::io::AsyncWriteExt; use tracing::error; @@ -198,7 +198,8 @@ pub trait Plugin { root_cert_client: RootCertClient, ) -> Result { let sm_path = sm_path(&module.name, &module.version, download_path); - let downloader = Downloader::new(sm_path, identity.map(|id| id.to_owned()), root_cert_client); + let downloader = + Downloader::new(sm_path, identity.map(|id| id.to_owned()), root_cert_client); if let Some(ref mut logger) = command_log { logger diff --git a/crates/core/tedge_agent/src/agent.rs b/crates/core/tedge_agent/src/agent.rs index ec856fd79e9..71cf4ba4dee 100644 --- a/crates/core/tedge_agent/src/agent.rs +++ b/crates/core/tedge_agent/src/agent.rs @@ -51,8 +51,8 @@ use tedge_mqtt_ext::TopicFilter; use tedge_script_ext::ScriptActor; use tedge_signal_ext::SignalActor; use tedge_uploader_ext::UploaderActor; -use tedge_utils::file::create_directory_with_defaults; use tedge_utils::certificates::RootCertClient; +use tedge_utils::file::create_directory_with_defaults; use tracing::info; use tracing::instrument; use tracing::warn; @@ -281,9 +281,13 @@ impl Agent { let tedge_to_te_converter = create_tedge_to_te_converter(&mut mqtt_actor_builder)?; let mut fs_watch_actor_builder = FsWatchActorBuilder::new(); - let mut downloader_actor_builder = - DownloaderActor::new(self.config.identity.clone(), self.config.root_cert_client.clone()).builder(); - let mut uploader_actor_builder = UploaderActor::new(self.config.identity).builder(); + let mut downloader_actor_builder = DownloaderActor::new( + self.config.identity.clone(), + self.config.root_cert_client.clone(), + ) + .builder(); + let mut uploader_actor_builder = + UploaderActor::new(self.config.identity, self.config.root_cert_client).builder(); // Instantiate config manager actor if config_snapshot or both operations are enabled let config_actor_builder: Option = diff --git a/crates/core/tedge_mapper/src/c8y/mapper.rs b/crates/core/tedge_mapper/src/c8y/mapper.rs index f50f2b4ffdc..ed579431955 100644 --- a/crates/core/tedge_mapper/src/c8y/mapper.rs +++ b/crates/core/tedge_mapper/src/c8y/mapper.rs @@ -202,7 +202,8 @@ impl TEdgeComponent for CumulocityMapper { let identity = tedge_config.http.client.auth.identity()?; let root_cert_client = tedge_config.root_cert_client(); - let mut uploader_actor = UploaderActor::new(identity.clone()).builder(); + let mut uploader_actor = + UploaderActor::new(identity.clone(), root_cert_client.clone()).builder(); let mut downloader_actor = DownloaderActor::new(identity, root_cert_client).builder(); // MQTT client dedicated to monitor the c8y-bridge client status and also diff --git a/crates/extensions/tedge_downloader_ext/src/actor.rs b/crates/extensions/tedge_downloader_ext/src/actor.rs index 117ab9470b5..6d6d52e8327 100644 --- a/crates/extensions/tedge_downloader_ext/src/actor.rs +++ b/crates/extensions/tedge_downloader_ext/src/actor.rs @@ -13,8 +13,8 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use tedge_utils::file::PermissionEntry; use tedge_utils::certificates::RootCertClient; +use tedge_utils::file::PermissionEntry; #[derive(Debug, Clone, Eq, PartialEq)] pub struct DownloadRequest { @@ -86,10 +86,7 @@ impl Clone for DownloaderActor { } impl DownloaderActor { - pub fn new( - identity: Option, - root_cert_client: RootCertClient, - ) -> Self { + pub fn new(identity: Option, root_cert_client: RootCertClient) -> Self { DownloaderActor { config: <_>::default(), key: PhantomData, diff --git a/crates/extensions/tedge_uploader_ext/Cargo.toml b/crates/extensions/tedge_uploader_ext/Cargo.toml index 7812e8b9c2c..ec31daa7d0e 100644 --- a/crates/extensions/tedge_uploader_ext/Cargo.toml +++ b/crates/extensions/tedge_uploader_ext/Cargo.toml @@ -15,6 +15,7 @@ camino = { workspace = true } log = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } tedge_actors = { workspace = true } +tedge_utils = { workspace = true } upload = { workspace = true } [dev-dependencies] diff --git a/crates/extensions/tedge_uploader_ext/src/actor.rs b/crates/extensions/tedge_uploader_ext/src/actor.rs index 2b15e316ee5..eca518af713 100644 --- a/crates/extensions/tedge_uploader_ext/src/actor.rs +++ b/crates/extensions/tedge_uploader_ext/src/actor.rs @@ -7,6 +7,7 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; +use tedge_utils::certificates::RootCertClient; use upload::Auth; use upload::ContentType; use upload::UploadError; @@ -84,13 +85,15 @@ pub type UploadResult = Result; pub struct UploaderActor { config: ServerConfig, identity: Option, + root_cert_client: RootCertClient, } impl UploaderActor { - pub fn new(identity: Option) -> Self { + pub fn new(identity: Option, root_cert_client: RootCertClient) -> Self { Self { config: ServerConfig::default(), identity, + root_cert_client, } } pub fn builder(self) -> ServerActorBuilder { @@ -101,7 +104,7 @@ impl UploaderActor { pub fn with_capacity(self, capacity: usize) -> Self { Self { config: self.config.with_capacity(capacity), - identity: self.identity, + ..self } } } @@ -125,7 +128,11 @@ impl Server for UploaderActor { upload_info = upload_info.with_auth(auth); } - let uploader = Uploader::new(request.file_path.clone(), self.identity.clone()); + let uploader = Uploader::new( + request.file_path.clone(), + self.identity.clone(), + self.root_cert_client.clone(), + ); info!( "Uploading from {} to url: {}", From c2850dfd9509e030b2baf209bcead2e00c619360 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Fri, 19 Jul 2024 09:11:06 +0100 Subject: [PATCH 05/17] Update tests to use Signed-off-by: James Rhodes --- .../download/examples/simple_download.rs | 3 ++- crates/common/download/src/download.rs | 24 +++++++++---------- crates/common/download/src/lib.rs | 4 +++- crates/common/tedge_utils/src/certificates.rs | 8 +++++++ crates/common/upload/src/lib.rs | 3 ++- crates/common/upload/src/upload.rs | 24 ++++++++++++++----- crates/core/plugin_sm/tests/plugin.rs | 5 ++++ crates/extensions/c8y_http_proxy/src/tests.rs | 4 ++++ .../tedge_downloader_ext/src/tests.rs | 7 ++++-- .../tedge_uploader_ext/src/tests.rs | 4 +++- 10 files changed, 62 insertions(+), 24 deletions(-) diff --git a/crates/common/download/examples/simple_download.rs b/crates/common/download/examples/simple_download.rs index cff1ad89d12..0939efee8da 100644 --- a/crates/common/download/examples/simple_download.rs +++ b/crates/common/download/examples/simple_download.rs @@ -1,6 +1,7 @@ use anyhow::Result; use download::DownloadInfo; use download::Downloader; +use tedge_utils::certificates::RootCertClient; /// This example shows how to use the `downloader`. #[tokio::main] @@ -12,7 +13,7 @@ async fn main() -> Result<()> { // Create downloader instance with desired file path and target directory. #[allow(deprecated)] - let downloader = Downloader::new("/tmp/test_download".into(), None); + let downloader = Downloader::new("/tmp/test_download".into(), None, RootCertClient::from([])); // Call `download` method to get data from url. downloader.download(&url_data).await?; diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 4b779f74f16..494a764d391 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -534,7 +534,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let mut downloader = Downloader::new(target_path, None); + let mut downloader = Downloader::new(target_path, None, RootCertClient::from([])); downloader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() @@ -564,7 +564,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path.clone(), None); + let downloader = Downloader::new(target_path.clone(), None, RootCertClient::from([])); downloader.download(&url).await.unwrap(); let file_content = std::fs::read(target_path).unwrap(); @@ -594,7 +594,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None); + let downloader = Downloader::new(target_path, None, RootCertClient::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!(err, DownloadError::InsufficientSpace)); } @@ -617,7 +617,7 @@ mod tests { let url = DownloadInfo::new(&target_url); // empty filename - let downloader = Downloader::new("".into(), None); + let downloader = Downloader::new("".into(), None, RootCertClient::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -626,7 +626,7 @@ mod tests { // invalid unicode filename let path = unsafe { String::from_utf8_unchecked(b"\xff".to_vec()) }; - let downloader = Downloader::new(path.into(), None); + let downloader = Downloader::new(path.into(), None, RootCertClient::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -634,7 +634,7 @@ mod tests { )); // relative path filename - let downloader = Downloader::new("myfile.txt".into(), None); + let downloader = Downloader::new("myfile.txt".into(), None, RootCertClient::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -661,7 +661,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_file_path.clone(), None); + let downloader = Downloader::new(target_file_path.clone(), None, RootCertClient::from([])); downloader.download(&url).await.unwrap(); let file_content = std::fs::read(target_file_path).unwrap(); @@ -688,7 +688,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None); + let downloader = Downloader::new(target_path, None, RootCertClient::from([])); downloader.download(&url).await.unwrap(); @@ -715,7 +715,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None); + let downloader = Downloader::new(target_path, None, RootCertClient::from([])); downloader.download(&url).await.unwrap(); let log_content = std::fs::read(downloader.filename()).unwrap(); @@ -736,7 +736,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None); + let downloader = Downloader::new(target_path, None, RootCertClient::from([])); downloader.download(&url).await.unwrap(); assert_eq!("".as_bytes(), std::fs::read(downloader.filename()).unwrap()); @@ -826,7 +826,7 @@ mod tests { let tmpdir = TempDir::new().unwrap(); let target_path = tmpdir.path().join("partial_download"); - let downloader = Downloader::new(target_path, None); + let downloader = Downloader::new(target_path, None, RootCertClient::from([])); let url = DownloadInfo::new(&format!("http://localhost:{port}/")); downloader.download(&url).await.unwrap(); @@ -934,7 +934,7 @@ mod tests { }; let target_path = target_dir_path.path().join("test_download"); - let mut downloader = Downloader::new(target_path, None); + let mut downloader = Downloader::new(target_path, None, RootCertClient::from([])); downloader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() diff --git a/crates/common/download/src/lib.rs b/crates/common/download/src/lib.rs index 62f45c5d08f..15651c7099f 100644 --- a/crates/common/download/src/lib.rs +++ b/crates/common/download/src/lib.rs @@ -29,8 +29,10 @@ //! "https://raw.githubusercontent.com/thin-edge/thin-edge.io/main/README.md", //! ); //! +//! let root_cert_client = unimplemented!("Get this from tedge config"); +//! //! // Create downloader instance with desired file path and target directory. -//! let downloader = Downloader::new("/tmp/test_download".into(), None); +//! let downloader = Downloader::new("/tmp/test_download".into(), None, root_cert_client); //! //! // Call `download` method to get data from url. //! downloader.download(&url_data).await?; diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/tedge_utils/src/certificates.rs index bd89dfcb0f1..271150dfddb 100644 --- a/crates/common/tedge_utils/src/certificates.rs +++ b/crates/common/tedge_utils/src/certificates.rs @@ -28,6 +28,14 @@ impl From> for RootCertClient { } } +impl From<[Certificate; 0]> for RootCertClient { + fn from(certificates: [Certificate; 0]) -> Self { + Self { + certificates: Arc::new(certificates), + } + } +} + /// Read a directory into a [RootCertStore] pub fn read_trust_store(ca_dir_or_file: &Utf8Path) -> anyhow::Result> { let mut certs = Vec::new(); diff --git a/crates/common/upload/src/lib.rs b/crates/common/upload/src/lib.rs index c8f543e5f36..e05378a56a1 100644 --- a/crates/common/upload/src/lib.rs +++ b/crates/common/upload/src/lib.rs @@ -25,8 +25,9 @@ //! ); //! //! let identity = unimplemented!("Get client certificate from configuration"); +//! let root_cert_client = unimplemented!("Get root_cert_client from configuration"); //! // Create uploader instance with source file path. -//! let uploader = Uploader::new("/tmp/test_upload".into(), identity); +//! let uploader = Uploader::new("/tmp/test_upload".into(), identity, root_cert_client); //! //! // Call `upload` method to send data to url. //! uploader.upload(&url_data).await?; diff --git a/crates/common/upload/src/upload.rs b/crates/common/upload/src/upload.rs index cc8b5ddda7d..a86f8daa211 100644 --- a/crates/common/upload/src/upload.rs +++ b/crates/common/upload/src/upload.rs @@ -316,7 +316,11 @@ mod tests { ttd.file("file_upload.txt") .with_raw_content("Hello, world!"); - let mut uploader = Uploader::new(ttd.utf8_path().join("file_upload.txt"), None); + let mut uploader = Uploader::new( + ttd.utf8_path().join("file_upload.txt"), + None, + RootCertClient::from([]), + ); uploader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() @@ -344,7 +348,11 @@ mod tests { ttd.file("file_upload.txt") .with_raw_content("Hello, world!"); - let mut uploader = Uploader::new(ttd.utf8_path().join("file_upload.txt"), None); + let mut uploader = Uploader::new( + ttd.utf8_path().join("file_upload.txt"), + None, + RootCertClient::from([]), + ); uploader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() @@ -374,7 +382,11 @@ mod tests { ttd.file("file_upload.txt") .with_raw_content("Hello, world!"); - let mut uploader = Uploader::new(ttd.utf8_path().join("file_upload.txt"), None); + let mut uploader = Uploader::new( + ttd.utf8_path().join("file_upload.txt"), + None, + RootCertClient::from([]), + ); uploader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, @@ -400,13 +412,13 @@ mod tests { // Not existing filename let source_path = Utf8Path::new("not_exist.txt").to_path_buf(); - let uploader = Uploader::new(source_path, None); + let uploader = Uploader::new(source_path, None, RootCertClient::from([])); assert!(uploader.upload(&url).await.is_err()); } #[test] fn default_uploader_uses_customised_backoff_parameters() { - let uploader = Uploader::new(Utf8PathBuf::default(), None); + let uploader = Uploader::new(Utf8PathBuf::default(), None, RootCertClient::from([])); assert_eq!(uploader.backoff.initial_interval, Duration::from_secs(15)); assert_eq!( @@ -486,7 +498,7 @@ mod tests { write_to_file_with_size(&mut source_file, 1024 * 1024).await; - let mut uploader = Uploader::new(source_path.to_owned(), None); + let mut uploader = Uploader::new(source_path.to_owned(), None, RootCertClient::from([])); // Adjust the backoff to be super fast for testing purposes uploader.set_backoff( ExponentialBackoffBuilder::new() diff --git a/crates/core/plugin_sm/tests/plugin.rs b/crates/core/plugin_sm/tests/plugin.rs index 5ad6769e77d..984741e11fc 100644 --- a/crates/core/plugin_sm/tests/plugin.rs +++ b/crates/core/plugin_sm/tests/plugin.rs @@ -13,6 +13,7 @@ mod tests { use tedge_api::SoftwareModule; use tedge_config::SudoCommandBuilder; use tedge_config::TEdgeConfigLocation; + use tedge_utils::certificates::RootCertClient; use test_case::test_case; #[test_case("abc", Some("1.0") ; "with version")] @@ -70,6 +71,7 @@ mod tests { None, None, config.http.client.auth.identity()?, + config.root_cert_client(), ); assert_eq!(plugin.name, "test"); assert_eq!(plugin.path, dummy_plugin_path); @@ -90,6 +92,7 @@ mod tests { None, None, None, + RootCertClient::from([]), ); let module = SoftwareModule { @@ -122,6 +125,7 @@ mod tests { None, None, None, + RootCertClient::from([]), ); // Create test module with name `test2`. @@ -160,6 +164,7 @@ mod tests { None, None, None, + RootCertClient::from([]), ); // Create software module without an explicit type. diff --git a/crates/extensions/c8y_http_proxy/src/tests.rs b/crates/extensions/c8y_http_proxy/src/tests.rs index 94077f6ecdb..439ea3ec22d 100644 --- a/crates/extensions/c8y_http_proxy/src/tests.rs +++ b/crates/extensions/c8y_http_proxy/src/tests.rs @@ -27,6 +27,7 @@ use tedge_http_ext::HttpActor; use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpRequestBuilder; use tedge_http_ext::HttpResult; +use tedge_utils::certificates::RootCertClient; use time::macros::datetime; #[tokio::test] @@ -359,6 +360,7 @@ async fn retry_internal_id_on_expired_jwt_with_mock() { device_id: external_id.into(), tmp_dir: tmp_dir.into(), identity: None, + root_cert_client: RootCertClient::from([]), retry_interval: Duration::from_millis(100), }; let c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http_actor, &mut jwt); @@ -424,6 +426,7 @@ async fn retry_create_event_on_expired_jwt_with_mock() { device_id: external_id.into(), tmp_dir: tmp_dir.into(), identity: None, + root_cert_client: RootCertClient::from([]), retry_interval: Duration::from_millis(100), }; let c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http_actor, &mut jwt); @@ -668,6 +671,7 @@ async fn spawn_c8y_http_proxy( device_id, tmp_dir, identity: None, + root_cert_client: RootCertClient::from([]), retry_interval: Duration::from_millis(10), }; let mut c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http, &mut jwt); diff --git a/crates/extensions/tedge_downloader_ext/src/tests.rs b/crates/extensions/tedge_downloader_ext/src/tests.rs index 2ad94f569a3..b26f6dbc970 100644 --- a/crates/extensions/tedge_downloader_ext/src/tests.rs +++ b/crates/extensions/tedge_downloader_ext/src/tests.rs @@ -3,6 +3,7 @@ use download::Auth; use std::time::Duration; use tedge_actors::ClientMessageBox; use tedge_test_utils::fs::TempTedgeDir; +use tedge_utils::certificates::RootCertClient; use tedge_utils::file::PermissionEntry; use tokio::time::timeout; @@ -109,7 +110,8 @@ async fn download_with_permission() { async fn spawn_downloader_actor( ) -> ClientMessageBox<(String, DownloadRequest), (String, DownloadResult)> { - let mut downloader_actor_builder = DownloaderActor::new(None).builder(); + let mut downloader_actor_builder = + DownloaderActor::new(None, RootCertClient::from([])).builder(); let requester = ClientMessageBox::new(&mut downloader_actor_builder); tokio::spawn(downloader_actor_builder.run()); @@ -159,7 +161,8 @@ struct TestDownloadKey { async fn spawn_downloader_actor_with_struct( ) -> ClientMessageBox<(TestDownloadKey, DownloadRequest), (TestDownloadKey, DownloadResult)> { - let mut downloader_actor_builder = DownloaderActor::new(None).builder(); + let mut downloader_actor_builder = + DownloaderActor::new(None, RootCertClient::from([])).builder(); let requester = ClientMessageBox::new(&mut downloader_actor_builder); tokio::spawn(downloader_actor_builder.run()); diff --git a/crates/extensions/tedge_uploader_ext/src/tests.rs b/crates/extensions/tedge_uploader_ext/src/tests.rs index 2253aa10ee2..41af6b50ca5 100644 --- a/crates/extensions/tedge_uploader_ext/src/tests.rs +++ b/crates/extensions/tedge_uploader_ext/src/tests.rs @@ -6,6 +6,7 @@ use camino::Utf8Path; use tedge_actors::ClientMessageBox; use tedge_actors::DynError; use tedge_test_utils::fs::TempTedgeDir; +use tedge_utils::certificates::RootCertClient; use tokio::time::timeout; use upload::Auth; @@ -18,7 +19,8 @@ const TEST_TIMEOUT: Duration = Duration::from_secs(5); async fn spawn_uploader_actor() -> ClientMessageBox<(String, UploadRequest), (String, UploadResult)> { let identity = None; - let mut uploader_actor_builder = UploaderActor::new(identity).builder(); + let mut uploader_actor_builder = + UploaderActor::new(identity, RootCertClient::from([])).builder(); let requester = ClientMessageBox::new(&mut uploader_actor_builder); tokio::spawn(uploader_actor_builder.run()); From 13ffb83e005007dee12a549e4aee60e524abc7fe Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Fri, 19 Jul 2024 13:03:04 +0100 Subject: [PATCH 06/17] Add support for hyper clients to use root cert path Signed-off-by: James Rhodes --- Cargo.lock | 4 ++ Cargo.toml | 1 + crates/common/certificate/Cargo.toml | 2 + .../certificate/src/parse_root_certificate.rs | 40 +++++++++++++++++++ .../src/tedge_config_cli/tedge_config.rs | 11 +++++ crates/core/tedge_mapper/src/c8y/mapper.rs | 2 +- crates/extensions/tedge_http_ext/Cargo.toml | 2 + crates/extensions/tedge_http_ext/src/actor.rs | 5 ++- crates/extensions/tedge_http_ext/src/lib.rs | 23 ++++++++--- 9 files changed, 81 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61b279ea66d..f76eaa6c050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,11 +837,13 @@ dependencies = [ "base64 0.13.1", "rcgen", "rustls 0.21.11", + "rustls-native-certs", "rustls-pemfile 1.0.4", "sha-1", "tempfile", "thiserror", "time", + "tracing", "x509-parser", "zeroize", ] @@ -4051,9 +4053,11 @@ dependencies = [ "hyper 0.14.28", "hyper-rustls", "mockito", + "rustls 0.21.11", "serde", "serde_json", "tedge_actors", + "tedge_config", "thiserror", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 309652301b8..4cb19647f22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ rumqttc = "0.23" # TODO: used git rev version to fix `unknown feature stdsimd` error: replace with 0.20 version after the release rumqttd = { git = "https://github.com/bytebeamio/rumqtt", rev = "0767080715699c34d8fe90b843716ba5ec12f8b9" } rustls = "0.21.11" +rustls-native-certs = "0.6.3" rustls-pemfile = "1.0.1" serde = "1.0" serde_ignored = "0.1" diff --git a/crates/common/certificate/Cargo.toml b/crates/common/certificate/Cargo.toml index b2e99219b8b..201342d6a75 100644 --- a/crates/common/certificate/Cargo.toml +++ b/crates/common/certificate/Cargo.toml @@ -11,10 +11,12 @@ repository = { workspace = true } [dependencies] rcgen = { workspace = true } rustls = { workspace = true } +rustls-native-certs = { workspace = true } rustls-pemfile = { workspace = true } sha-1 = { workspace = true } thiserror = { workspace = true } time = { workspace = true } +tracing = { workspace = true } x509-parser = { workspace = true } zeroize = { workspace = true } diff --git a/crates/common/certificate/src/parse_root_certificate.rs b/crates/common/certificate/src/parse_root_certificate.rs index 5682835650c..126a1a6598d 100644 --- a/crates/common/certificate/src/parse_root_certificate.rs +++ b/crates/common/certificate/src/parse_root_certificate.rs @@ -28,6 +28,46 @@ pub fn create_tls_config( .with_client_auth_cert(cert_chain, pvt_key)?) } +pub fn client_config_for_ca_certificates

( + root_certificates: impl IntoIterator, +) -> Result +where + P: AsRef, +{ + let mut roots = RootCertStore::empty(); + for cert_path in root_certificates { + rec_add_root_cert(&mut roots, cert_path.as_ref()); + } + + let (mut valid_count, mut invalid_count) = (0, 0); + for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs") { + match roots.add(&Certificate(cert.0)) { + Ok(_) => valid_count += 1, + Err(err) => { + tracing::debug!("certificate parsing failed: {:?}", err); + invalid_count += 1 + } + } + } + tracing::debug!( + "with_native_roots processed {} valid and {} invalid certs", + valid_count, + invalid_count + ); + if roots.is_empty() { + tracing::debug!("no valid root CA certificates found"); + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("no valid root CA certificates found ({invalid_count} invalid)"), + ))? + } + + Ok(ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(roots) + .with_no_client_auth()) +} + pub fn add_certs_from_file( root_store: &mut RootCertStore, cert_file: impl AsRef, diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index a90129cc761..206c86468b6 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -14,6 +14,7 @@ use anyhow::anyhow; use anyhow::ensure; use anyhow::Context; use camino::Utf8PathBuf; +use certificate::parse_root_certificate::client_config_for_ca_certificates; use certificate::parse_root_certificate::create_tls_config; use certificate::CertificateError; use certificate::PemCertificate; @@ -1000,6 +1001,16 @@ impl TEdgeConfigReader { RootCertClient::from(roots.clone()) } + + pub fn cloud_client_tls_config(&self) -> rustls::ClientConfig { + // TODO do we want to unwrap here? + client_config_for_ca_certificates([ + &self.c8y.root_cert_path, + &self.az.root_cert_path, + &self.aws.root_cert_path, + ]) + .unwrap() + } } fn c8y_topic_prefix() -> TopicPrefix { diff --git a/crates/core/tedge_mapper/src/c8y/mapper.rs b/crates/core/tedge_mapper/src/c8y/mapper.rs index ed579431955..107a81ef1e0 100644 --- a/crates/core/tedge_mapper/src/c8y/mapper.rs +++ b/crates/core/tedge_mapper/src/c8y/mapper.rs @@ -190,7 +190,7 @@ impl TEdgeComponent for CumulocityMapper { mqtt_config.clone(), tedge_config.c8y.bridge.topic_prefix.clone(), ); - let mut http_actor = HttpActor::new().builder(); + let mut http_actor = HttpActor::new(&tedge_config).builder(); let c8y_http_config = (&tedge_config).try_into()?; let mut c8y_http_proxy_actor = C8YHttpProxyBuilder::new(c8y_http_config, &mut http_actor, &mut jwt_actor); diff --git a/crates/extensions/tedge_http_ext/Cargo.toml b/crates/extensions/tedge_http_ext/Cargo.toml index 0f624c65c7f..3d7bca539c8 100644 --- a/crates/extensions/tedge_http_ext/Cargo.toml +++ b/crates/extensions/tedge_http_ext/Cargo.toml @@ -25,9 +25,11 @@ hyper = { workspace = true, default_features = false, features = [ "tcp", ] } hyper-rustls = { workspace = true } +rustls = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tedge_actors = { workspace = true } +tedge_config = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, default_features = false, features = [ "macros", diff --git a/crates/extensions/tedge_http_ext/src/actor.rs b/crates/extensions/tedge_http_ext/src/actor.rs index f9fc8df69c4..3f4e98efb79 100644 --- a/crates/extensions/tedge_http_ext/src/actor.rs +++ b/crates/extensions/tedge_http_ext/src/actor.rs @@ -6,6 +6,7 @@ use hyper::client::Client; use hyper::client::HttpConnector; use hyper_rustls::HttpsConnector; use hyper_rustls::HttpsConnectorBuilder; +use rustls::ClientConfig; use tedge_actors::Server; #[derive(Clone)] @@ -14,9 +15,9 @@ pub struct HttpService { } impl HttpService { - pub(crate) fn new() -> Self { + pub(crate) fn new(client_config: ClientConfig) -> Self { let https = HttpsConnectorBuilder::new() - .with_native_roots() + .with_tls_config(client_config) .https_or_http() .enable_http1() .enable_http2() diff --git a/crates/extensions/tedge_http_ext/src/lib.rs b/crates/extensions/tedge_http_ext/src/lib.rs index 0c0c9f75c7b..99c6b507bd4 100644 --- a/crates/extensions/tedge_http_ext/src/lib.rs +++ b/crates/extensions/tedge_http_ext/src/lib.rs @@ -13,30 +13,41 @@ use actor::*; use tedge_actors::Concurrent; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; +use tedge_config::TEdgeConfig; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct HttpActor { config: ServerConfig, + tls_client_config: rustls::ClientConfig, } impl HttpActor { - pub fn new() -> Self { - HttpActor::default() + pub fn new(tedge_config: &TEdgeConfig) -> Self { + Self { + config: <_>::default(), + tls_client_config: tedge_config.cloud_client_tls_config(), + } } pub fn builder(&self) -> ServerActorBuilder { - ServerActorBuilder::new(HttpService::new(), &self.config, Concurrent) + ServerActorBuilder::new( + HttpService::new(self.tls_client_config.clone()), + &self.config, + Concurrent, + ) } - pub fn with_capacity(self, capacity: usize) -> Self { + pub fn with_capacity(self, capacity: usize, tedge_config: &TEdgeConfig) -> Self { Self { config: self.config.with_capacity(capacity), + ..Self::new(tedge_config) } } - pub fn with_max_concurrency(self, max_concurrency: usize) -> Self { + pub fn with_max_concurrency(self, max_concurrency: usize, tedge_config: &TEdgeConfig) -> Self { Self { config: self.config.with_max_concurrency(max_concurrency), + ..Self::new(tedge_config) } } } From 942e39e1a0a9417a414ad3ffa5e6c6a9c506a51c Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Mon, 22 Jul 2024 11:10:48 +0100 Subject: [PATCH 07/17] Add support to remote access plugin for root_cert_path Signed-off-by: James Rhodes --- Cargo.lock | 3 + clippy.toml | 2 +- crates/common/axum_tls/src/acceptor.rs | 1 + crates/common/axum_tls/src/files.rs | 10 ++- crates/core/plugin_sm/src/plugin.rs | 1 + .../src/file_transfer_server/actor.rs | 2 + crates/extensions/c8y_auth_proxy/Cargo.toml | 1 + crates/extensions/c8y_auth_proxy/src/actor.rs | 2 + .../extensions/c8y_auth_proxy/src/server.rs | 76 ++++++++----------- crates/extensions/c8y_http_proxy/Cargo.toml | 1 + crates/extensions/c8y_http_proxy/src/tests.rs | 12 ++- crates/extensions/tedge_http_ext/Cargo.toml | 1 + crates/extensions/tedge_http_ext/src/tests.rs | 7 +- plugins/c8y_remote_access_plugin/src/lib.rs | 4 +- plugins/c8y_remote_access_plugin/src/proxy.rs | 22 ++++-- 15 files changed, 82 insertions(+), 63 deletions(-) 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), From 30388dc5584f68ff52a4afbd28be99844e8334ad Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Mon, 22 Jul 2024 12:35:35 +0100 Subject: [PATCH 08/17] Add test to verify remote access works with a custom root cert path Signed-off-by: James Rhodes --- ..._remote_access_custom_root_cert_path.robot | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot diff --git a/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot new file mode 100644 index 00000000000..513ad6b3f20 --- /dev/null +++ b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot @@ -0,0 +1,27 @@ +*** Settings *** +Resource ../../../resources/common.resource +Library Cumulocity +Library ThinEdgeIO + +Test Tags theme:c8y theme:troubleshooting theme:plugins adapter:docker +Test Setup Custom Setup +Test Teardown Get Logs + +*** Test Cases *** + +Execute ssh command with a custom root certificate path + ${KEY_FILE}= Configure SSH + Add Remote Access Passthrough Configuration + ${stdout}= Execute Remote Access Command command=tedge --version exp_exit_code=0 user=root key_file=${KEY_FILE} + Should Match Regexp ${stdout} tedge .+ + +*** Keywords *** + +Custom Setup + ${DEVICE_SN}= Setup + Set Suite Variable $DEVICE_SN + Device Should Exist ${DEVICE_SN} + Enable Service ssh + Start Service ssh + Execute Command mv /etc/ssl/certs /etc/ssl/moved-certs + Execute Command tedge config set c8y.root_cert_path /etc/ssl/moved-certs From 9cf7376c2601cde1e741db53780aa56c1c30a4eb Mon Sep 17 00:00:00 2001 From: gligorisaev Date: Wed, 3 Jul 2024 12:03:44 +0200 Subject: [PATCH 09/17] proposal test suite for this PR Signed-off-by: gligorisaev --- .../tests/tedge/parse_root_certificate.robot | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/RobotFramework/tests/tedge/parse_root_certificate.robot diff --git a/tests/RobotFramework/tests/tedge/parse_root_certificate.robot b/tests/RobotFramework/tests/tedge/parse_root_certificate.robot new file mode 100644 index 00000000000..3d547445ad4 --- /dev/null +++ b/tests/RobotFramework/tests/tedge/parse_root_certificate.robot @@ -0,0 +1,71 @@ +*** Settings *** +Resource ../../resources/common.resource +Library ThinEdgeIO +Library String +Library Collections + +Test Teardown Get Logs + +Test Tags theme:ca_certificates + + +*** Test Cases *** + +Verify Single Certificate File + [Setup] Setup With Self-Signed Certificate + ThinEdgeIO.File Should Exist /etc/tedge/device-certs/tedge-certificate.pem + ${output_cert}= Execute Command cat /etc/tedge/device-certs/tedge-certificate.pem + Should Contain ${output_cert} -----BEGIN CERTIFICATE----- + +Verify Single Private Key File + [Setup] Setup With Self-Signed Certificate + ThinEdgeIO.File Should Exist /etc/tedge/device-certs/tedge-private-key.pem + ${output_key}= Execute Command cat /etc/tedge/device-certs/tedge-private-key.pem + Should Contain ${output_key} -----BEGIN PRIVATE KEY----- + +Verify Multiple Certificates in Directory + [Setup] Setup With Self-Signed Certificate + ThinEdgeIO.File Should Exist /etc/tedge/device-certs/tedge-certificate.pem + ${dir_contents}= Execute Command ls /etc/tedge/device-certs + Log ${dir_contents} + + # List .pem files and check the result + ${cert_files}= Execute Command ls /etc/tedge/device-certs/*.pem + ${cert_files_list}= Split String ${cert_files} \n + @{filtered_cert_files}= Create List + FOR ${file} IN @{cert_files_list} + Run Keyword If '${file}' != '' Append To List ${filtered_cert_files} ${file} + END + + ${cert_files_length}= Get Length ${filtered_cert_files} + Should Be True ${cert_files_length} > 1 + FOR ${cert_file} IN @{filtered_cert_files} + ${output_cert}= Execute Command cat ${cert_file} + Should Contain Any Line ${output_cert} ${cert_file} + END + +Verify Invalid Path + [Setup] Setup With Self-Signed Certificate + ${result}= Execute Command ls /invalid/path/*.pem ignore_exit_code=True stderr=True stdout=True + ${stdout}= Set Variable ${result}[1] + Should Contain ${stdout} No such file or directory + + +*** Keywords *** +Setup With Self-Signed Certificate + ${DEVICE_SN}= Setup skip_bootstrap=${True} + Set Test Variable $DEVICE_SN + Execute Command test -f ./bootstrap.sh && ./bootstrap.sh --cert-method selfsigned + +Setup Without Certificate + ${DEVICE_SN}= Setup skip_bootstrap=${True} + Set Test Variable $DEVICE_SN + Execute Command test -f ./bootstrap.sh && ./bootstrap.sh --install --no-bootstrap --no-connect + +Should Contain Any Line + [Arguments] ${text} ${cert_file} + ${cert_present}= Run Keyword And Return Status Should Contain ${text} -----BEGIN CERTIFICATE----- + ${key_present}= Run Keyword And Return Status Should Contain ${text} -----BEGIN PRIVATE KEY----- + Run Keyword If ${cert_present} Log Certificate found in ${cert_file} + Run Keyword If ${key_present} Log Private key found in ${cert_file} + Run Keyword If not ${cert_present} and not ${key_present} Fail The file ${cert_file} does not contain a valid certificate or private key. From 1db32b4384b3508886bea4f5a05c94b5e1d4fd6d Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 23 Jul 2024 09:45:11 +0100 Subject: [PATCH 10/17] Verify certificates can be uploaded with custom root cert path --- ...ge_upload_cert_custom_root_cert_path.robot | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/RobotFramework/tests/tedge/tedge_upload_cert_custom_root_cert_path.robot diff --git a/tests/RobotFramework/tests/tedge/tedge_upload_cert_custom_root_cert_path.robot b/tests/RobotFramework/tests/tedge/tedge_upload_cert_custom_root_cert_path.robot new file mode 100644 index 00000000000..f1ddc07852d --- /dev/null +++ b/tests/RobotFramework/tests/tedge/tedge_upload_cert_custom_root_cert_path.robot @@ -0,0 +1,27 @@ +*** Settings *** +Documentation Run certificate upload test and fails to upload the cert because there is no root certificate directory, +... Then check the negative response in stderr + +Resource ../../resources/common.resource +Library ThinEdgeIO + +Test Tags theme:cli theme:mqtt theme:c8y adapter:docker +Test Teardown Get Logs + +*** Test Cases *** +tedge cert upload c8y respects root cert path + [Setup] Setup With Self-Signed Certificate + Execute Command sudo tedge disconnect c8y + ${output}= Execute Command sudo tedge cert renew stderr=${True} stdout=${False} ignore_exit_code=${True} + Should Contain ${output} Certificate was successfully renewed, for un-interrupted service, the certificate has to be uploaded to the cloud + Execute Command mv /etc/ssl/certs /etc/ssl/certs_test + Execute Command tedge config set c8y.root_cert_path /etc/ssl/certs_test + Execute Command cmd=sudo env C8Y_USER='${C8Y_CONFIG.username}' C8Y_PASSWORD='${C8Y_CONFIG.password}' tedge cert upload c8y + ${output}= Execute Command sudo tedge connect c8y + Should Contain ${output} Connection check is successful. + +*** Keywords *** +Setup With Self-Signed Certificate + ${DEVICE_SN}= Setup skip_bootstrap=${True} + Set Test Variable $DEVICE_SN + Execute Command test -f ./bootstrap.sh && ./bootstrap.sh --cert-method selfsigned From 66aaa8089975cc22f78d49aa35f06aca402a3d5e Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 23 Jul 2024 14:29:49 +0100 Subject: [PATCH 11/17] Address some review comments --- Cargo.lock | 1 + clippy.toml | 11 ++++-- crates/common/certificate/Cargo.toml | 1 + crates/common/clippy.toml | 8 ----- .../download/examples/simple_download.rs | 4 +-- crates/common/download/src/download.rs | 34 +++++++++---------- crates/common/download/src/lib.rs | 4 +-- .../src/tedge_config_cli/tedge_config.rs | 6 ++-- crates/common/tedge_utils/src/certificates.rs | 14 ++++---- crates/common/upload/src/lib.rs | 4 +-- crates/common/upload/src/upload.rs | 22 ++++++------ crates/core/plugin_sm/src/plugin.rs | 26 +++++++------- crates/core/plugin_sm/src/plugin_manager.rs | 2 +- crates/core/plugin_sm/tests/plugin.rs | 10 +++--- crates/core/tedge_agent/src/agent.rs | 12 +++---- crates/core/tedge_mapper/src/c8y/mapper.rs | 6 ++-- crates/extensions/c8y_auth_proxy/src/actor.rs | 2 +- crates/extensions/c8y_http_proxy/src/actor.rs | 2 +- crates/extensions/c8y_http_proxy/src/lib.rs | 8 ++--- crates/extensions/c8y_http_proxy/src/tests.rs | 8 ++--- .../tedge_downloader_ext/src/actor.rs | 25 +++++--------- .../tedge_downloader_ext/src/tests.rs | 6 ++-- crates/extensions/tedge_http_ext/Cargo.toml | 4 +-- crates/extensions/tedge_http_ext/src/lib.rs | 8 ++--- .../tedge_uploader_ext/src/actor.rs | 10 +++--- .../tedge_uploader_ext/src/tests.rs | 4 +-- plugins/c8y_firmware_plugin/src/lib.rs | 4 +-- 27 files changed, 120 insertions(+), 126 deletions(-) delete mode 100644 crates/common/clippy.toml diff --git a/Cargo.lock b/Cargo.lock index f858e61b759..658ac6c4dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,6 +838,7 @@ dependencies = [ "assert_matches", "base64 0.13.1", "rcgen", + "reqwest", "rustls 0.21.11", "rustls-native-certs", "rustls-pemfile 1.0.4", diff --git a/clippy.toml b/clippy.toml index 6658f218eca..9a05eb07870 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,9 @@ -disallowed-types = ["reqwest::ClientBuilder"] -disallowed-methods = ["reqwest::Client::builder", "reqwest::Client::new", "hyper::client::Client::new"] +disallowed-types = [ + { path = "reqwest::ClientBuilder", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" } +] +disallowed-methods = [ + { path = "reqwest::Client::builder", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" }, + { path = "reqwest::Client::new", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" }, + { path = "hyper::client::Client::new", reason = "Use Client::builder()" }, + { path = "hyper_rustls::HttpsConnectorBuilder::with_native_roots", reason = "Use .with_tls_config(tedge_config.cloud_client_tls_config()) instead to use configured root certificate paths for the connected cloud" }, +] diff --git a/crates/common/certificate/Cargo.toml b/crates/common/certificate/Cargo.toml index 201342d6a75..95c5ec47395 100644 --- a/crates/common/certificate/Cargo.toml +++ b/crates/common/certificate/Cargo.toml @@ -10,6 +10,7 @@ repository = { workspace = true } [dependencies] rcgen = { workspace = true } +reqwest = { workspace = true } rustls = { workspace = true } rustls-native-certs = { workspace = true } rustls-pemfile = { workspace = true } diff --git a/crates/common/clippy.toml b/crates/common/clippy.toml deleted file mode 100644 index 5206fb3abbf..00000000000 --- a/crates/common/clippy.toml +++ /dev/null @@ -1,8 +0,0 @@ -disallowed-types = [ - # "reqwest::ClientBuilder", - # "reqwest::Client", -] - -disallowed-methods = [ - "reqwest::Client::builder" -] \ No newline at end of file diff --git a/crates/common/download/examples/simple_download.rs b/crates/common/download/examples/simple_download.rs index 0939efee8da..c460b9cfd3d 100644 --- a/crates/common/download/examples/simple_download.rs +++ b/crates/common/download/examples/simple_download.rs @@ -1,7 +1,7 @@ use anyhow::Result; use download::DownloadInfo; use download::Downloader; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; /// This example shows how to use the `downloader`. #[tokio::main] @@ -13,7 +13,7 @@ async fn main() -> Result<()> { // Create downloader instance with desired file path and target directory. #[allow(deprecated)] - let downloader = Downloader::new("/tmp/test_download".into(), None, RootCertClient::from([])); + let downloader = Downloader::new("/tmp/test_download".into(), None, CloudRootCerts::from([])); // Call `download` method to get data from url. downloader.download(&url_data).await?; diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 494a764d391..1c445169f68 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -24,7 +24,7 @@ use std::os::unix::prelude::AsRawFd; use std::path::Path; use std::path::PathBuf; use std::time::Duration; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tedge_utils::file::move_file; use tedge_utils::file::FileError; use tedge_utils::file::PermissionEntry; @@ -117,9 +117,9 @@ impl Downloader { pub fn new( target_path: PathBuf, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> Self { - let mut client_builder = root_cert_client.builder(); + let mut client_builder = cloud_root_certs.client_builder(); if let Some(identity) = identity { client_builder = client_builder.identity(identity); } @@ -139,9 +139,9 @@ impl Downloader { target_path: PathBuf, target_permission: PermissionEntry, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> Self { - let mut client_builder = root_cert_client.builder(); + let mut client_builder = cloud_root_certs.client_builder(); if let Some(identity) = identity { client_builder = client_builder.identity(identity); } @@ -534,7 +534,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let mut downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let mut downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); downloader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() @@ -564,7 +564,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path.clone(), None, RootCertClient::from([])); + let downloader = Downloader::new(target_path.clone(), None, CloudRootCerts::from([])); downloader.download(&url).await.unwrap(); let file_content = std::fs::read(target_path).unwrap(); @@ -594,7 +594,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!(err, DownloadError::InsufficientSpace)); } @@ -617,7 +617,7 @@ mod tests { let url = DownloadInfo::new(&target_url); // empty filename - let downloader = Downloader::new("".into(), None, RootCertClient::from([])); + let downloader = Downloader::new("".into(), None, CloudRootCerts::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -626,7 +626,7 @@ mod tests { // invalid unicode filename let path = unsafe { String::from_utf8_unchecked(b"\xff".to_vec()) }; - let downloader = Downloader::new(path.into(), None, RootCertClient::from([])); + let downloader = Downloader::new(path.into(), None, CloudRootCerts::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -634,7 +634,7 @@ mod tests { )); // relative path filename - let downloader = Downloader::new("myfile.txt".into(), None, RootCertClient::from([])); + let downloader = Downloader::new("myfile.txt".into(), None, CloudRootCerts::from([])); let err = downloader.download(&url).await.unwrap_err(); assert!(matches!( err, @@ -661,7 +661,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_file_path.clone(), None, RootCertClient::from([])); + let downloader = Downloader::new(target_file_path.clone(), None, CloudRootCerts::from([])); downloader.download(&url).await.unwrap(); let file_content = std::fs::read(target_file_path).unwrap(); @@ -688,7 +688,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); downloader.download(&url).await.unwrap(); @@ -715,7 +715,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); downloader.download(&url).await.unwrap(); let log_content = std::fs::read(downloader.filename()).unwrap(); @@ -736,7 +736,7 @@ mod tests { let url = DownloadInfo::new(&target_url); - let downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); downloader.download(&url).await.unwrap(); assert_eq!("".as_bytes(), std::fs::read(downloader.filename()).unwrap()); @@ -826,7 +826,7 @@ mod tests { let tmpdir = TempDir::new().unwrap(); let target_path = tmpdir.path().join("partial_download"); - let downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); let url = DownloadInfo::new(&format!("http://localhost:{port}/")); downloader.download(&url).await.unwrap(); @@ -934,7 +934,7 @@ mod tests { }; let target_path = target_dir_path.path().join("test_download"); - let mut downloader = Downloader::new(target_path, None, RootCertClient::from([])); + let mut downloader = Downloader::new(target_path, None, CloudRootCerts::from([])); downloader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, ..Default::default() diff --git a/crates/common/download/src/lib.rs b/crates/common/download/src/lib.rs index 15651c7099f..829ae45736a 100644 --- a/crates/common/download/src/lib.rs +++ b/crates/common/download/src/lib.rs @@ -29,10 +29,10 @@ //! "https://raw.githubusercontent.com/thin-edge/thin-edge.io/main/README.md", //! ); //! -//! let root_cert_client = unimplemented!("Get this from tedge config"); +//! let cloud_root_certs = unimplemented!("Get this from tedge config"); //! //! // Create downloader instance with desired file path and target directory. -//! let downloader = Downloader::new("/tmp/test_download".into(), None, root_cert_client); +//! let downloader = Downloader::new("/tmp/test_download".into(), None, cloud_root_certs); //! //! // Call `download` method to get data from url. //! downloader.download(&url_data).await?; diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index 206c86468b6..5b988aad66a 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -42,7 +42,7 @@ use tedge_config_macros::struct_field_paths; pub use tedge_config_macros::ConfigNotSet; use tedge_config_macros::OptionalConfig; use tedge_utils::certificates::read_trust_store; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use toml::Table; use tracing::error; @@ -969,7 +969,7 @@ define_tedge_config! { static CLOUD_ROOT_CERTIFICATES: OnceLock> = OnceLock::new(); impl TEdgeConfigReader { - pub fn root_cert_client(&self) -> RootCertClient { + pub fn cloud_root_certs(&self) -> CloudRootCerts { let roots = CLOUD_ROOT_CERTIFICATES.get_or_init(|| { let c8y_roots = read_trust_store(&self.c8y.root_cert_path).unwrap_or_else(move |e| { error!( @@ -999,7 +999,7 @@ impl TEdgeConfigReader { .collect() }); - RootCertClient::from(roots.clone()) + CloudRootCerts::from(roots.clone()) } pub fn cloud_client_tls_config(&self) -> rustls::ClientConfig { diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/tedge_utils/src/certificates.rs index 271150dfddb..923a7640df4 100644 --- a/crates/common/tedge_utils/src/certificates.rs +++ b/crates/common/tedge_utils/src/certificates.rs @@ -2,33 +2,33 @@ use anyhow::Context; use camino::Utf8Path; use camino::Utf8PathBuf; use reqwest::Certificate; -use reqwest::ClientBuilder; use std::fs::File; use std::sync::Arc; #[derive(Debug, Clone)] -pub struct RootCertClient { +pub struct CloudRootCerts { certificates: Arc<[Certificate]>, } -impl RootCertClient { - pub fn builder(&self) -> ClientBuilder { +impl CloudRootCerts { + #[allow(clippy::disallowed_types)] + pub fn client_builder(&self) -> reqwest::ClientBuilder { self.certificates .iter() .cloned() - .fold(ClientBuilder::new(), |builder, cert| { + .fold(reqwest::ClientBuilder::new(), |builder, cert| { builder.add_root_certificate(cert) }) } } -impl From> for RootCertClient { +impl From> for CloudRootCerts { fn from(certificates: Arc<[Certificate]>) -> Self { Self { certificates } } } -impl From<[Certificate; 0]> for RootCertClient { +impl From<[Certificate; 0]> for CloudRootCerts { fn from(certificates: [Certificate; 0]) -> Self { Self { certificates: Arc::new(certificates), diff --git a/crates/common/upload/src/lib.rs b/crates/common/upload/src/lib.rs index e05378a56a1..4deabc05cfe 100644 --- a/crates/common/upload/src/lib.rs +++ b/crates/common/upload/src/lib.rs @@ -25,9 +25,9 @@ //! ); //! //! let identity = unimplemented!("Get client certificate from configuration"); -//! let root_cert_client = unimplemented!("Get root_cert_client from configuration"); +//! let cloud_root_certs = unimplemented!("Get cloud_root_certs from configuration"); //! // Create uploader instance with source file path. -//! let uploader = Uploader::new("/tmp/test_upload".into(), identity, root_cert_client); +//! let uploader = Uploader::new("/tmp/test_upload".into(), identity, cloud_root_certs); //! //! // Call `upload` method to send data to url. //! uploader.upload(&url_data).await?; diff --git a/crates/common/upload/src/upload.rs b/crates/common/upload/src/upload.rs index a86f8daa211..204bc43f527 100644 --- a/crates/common/upload/src/upload.rs +++ b/crates/common/upload/src/upload.rs @@ -13,7 +13,7 @@ use reqwest::multipart; use reqwest::Body; use reqwest::Identity; use std::time::Duration; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tokio::fs::File; use tokio_util::codec::BytesCodec; use tokio_util::codec::FramedRead; @@ -133,20 +133,20 @@ pub struct Uploader { source_filename: Utf8PathBuf, backoff: ExponentialBackoff, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, } impl Uploader { pub fn new( target_path: Utf8PathBuf, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> Self { Self { source_filename: target_path, backoff: default_backoff(), identity, - root_cert_client, + cloud_root_certs, } } @@ -181,7 +181,7 @@ impl Uploader { let file_body = Body::wrap_stream(FramedRead::new(file, BytesCodec::new())); - let mut client = self.root_cert_client.builder(); + let mut client = self.cloud_root_certs.client_builder(); if let Some(identity) = self.identity.clone() { client = client.identity(identity); } @@ -319,7 +319,7 @@ mod tests { let mut uploader = Uploader::new( ttd.utf8_path().join("file_upload.txt"), None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); uploader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, @@ -351,7 +351,7 @@ mod tests { let mut uploader = Uploader::new( ttd.utf8_path().join("file_upload.txt"), None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); uploader.set_backoff(ExponentialBackoff { current_interval: Duration::ZERO, @@ -385,7 +385,7 @@ mod tests { let mut uploader = Uploader::new( ttd.utf8_path().join("file_upload.txt"), None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); uploader.set_backoff(ExponentialBackoff { @@ -412,13 +412,13 @@ mod tests { // Not existing filename let source_path = Utf8Path::new("not_exist.txt").to_path_buf(); - let uploader = Uploader::new(source_path, None, RootCertClient::from([])); + let uploader = Uploader::new(source_path, None, CloudRootCerts::from([])); assert!(uploader.upload(&url).await.is_err()); } #[test] fn default_uploader_uses_customised_backoff_parameters() { - let uploader = Uploader::new(Utf8PathBuf::default(), None, RootCertClient::from([])); + let uploader = Uploader::new(Utf8PathBuf::default(), None, CloudRootCerts::from([])); assert_eq!(uploader.backoff.initial_interval, Duration::from_secs(15)); assert_eq!( @@ -498,7 +498,7 @@ mod tests { write_to_file_with_size(&mut source_file, 1024 * 1024).await; - let mut uploader = Uploader::new(source_path.to_owned(), None, RootCertClient::from([])); + let mut uploader = Uploader::new(source_path.to_owned(), None, CloudRootCerts::from([])); // Adjust the backoff to be super fast for testing purposes uploader.set_backoff( ExponentialBackoffBuilder::new() diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index 1e402c68d77..45bf3a9f49b 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -17,7 +17,7 @@ use tedge_api::SoftwareModuleUpdate; use tedge_api::SoftwareType; use tedge_api::DEFAULT; use tedge_config::SudoCommandBuilder; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tokio::io::AsyncWriteExt; use tracing::error; @@ -73,7 +73,7 @@ pub trait Plugin { command_log, download_path, self.identity(), - self.root_cert_client().clone(), + self.cloud_root_certs().clone(), ) .await? } @@ -87,7 +87,7 @@ pub trait Plugin { } fn identity(&self) -> Option<&Identity>; - fn root_cert_client(&self) -> &RootCertClient; + fn cloud_root_certs(&self) -> &CloudRootCerts; async fn apply_all( &self, @@ -118,7 +118,7 @@ pub trait Plugin { command_log.as_deref_mut(), download_path, self.identity(), - self.root_cert_client().clone(), + self.cloud_root_certs().clone(), ) .await { @@ -172,7 +172,7 @@ pub trait Plugin { mut command_log: Option<&mut CommandLog>, download_path: &Path, identity: Option<&Identity>, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> Result<(), SoftwareError> { let downloader = Self::download_from_url( module, @@ -180,7 +180,7 @@ pub trait Plugin { command_log.as_deref_mut(), download_path, identity, - root_cert_client, + cloud_root_certs, ) .await?; let result = self.install(module, command_log.as_deref_mut()).await; @@ -195,11 +195,11 @@ pub trait Plugin { mut command_log: Option<&mut CommandLog>, download_path: &Path, identity: Option<&Identity>, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> Result { let sm_path = sm_path(&module.name, &module.version, download_path); let downloader = - Downloader::new(sm_path, identity.map(|id| id.to_owned()), root_cert_client); + Downloader::new(sm_path, identity.map(|id| id.to_owned()), cloud_root_certs); if let Some(ref mut logger) = command_log { logger @@ -274,7 +274,7 @@ pub struct ExternalPluginCommand { exclude: Option, include: Option, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, } impl ExternalPluginCommand { @@ -287,7 +287,7 @@ impl ExternalPluginCommand { exclude: Option, include: Option, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, ) -> ExternalPluginCommand { ExternalPluginCommand { name: name.into(), @@ -297,7 +297,7 @@ impl ExternalPluginCommand { exclude, include, identity, - root_cert_client, + cloud_root_certs, } } @@ -584,8 +584,8 @@ impl Plugin for ExternalPluginCommand { self.identity.as_ref() } - fn root_cert_client(&self) -> &RootCertClient { - &self.root_cert_client + fn cloud_root_certs(&self) -> &CloudRootCerts { + &self.cloud_root_certs } } diff --git a/crates/core/plugin_sm/src/plugin_manager.rs b/crates/core/plugin_sm/src/plugin_manager.rs index 7d91da077b2..a8a52570a7f 100644 --- a/crates/core/plugin_sm/src/plugin_manager.rs +++ b/crates/core/plugin_sm/src/plugin_manager.rs @@ -210,7 +210,7 @@ impl ExternalPlugins { config.software.plugin.exclude.or_none().cloned(), config.software.plugin.include.or_none().cloned(), identity, - config.root_cert_client(), + config.cloud_root_certs(), ); self.plugin_map.insert(plugin_name.into(), plugin); } diff --git a/crates/core/plugin_sm/tests/plugin.rs b/crates/core/plugin_sm/tests/plugin.rs index 984741e11fc..b6353cc36e3 100644 --- a/crates/core/plugin_sm/tests/plugin.rs +++ b/crates/core/plugin_sm/tests/plugin.rs @@ -13,7 +13,7 @@ mod tests { use tedge_api::SoftwareModule; use tedge_config::SudoCommandBuilder; use tedge_config::TEdgeConfigLocation; - use tedge_utils::certificates::RootCertClient; + use tedge_utils::certificates::CloudRootCerts; use test_case::test_case; #[test_case("abc", Some("1.0") ; "with version")] @@ -71,7 +71,7 @@ mod tests { None, None, config.http.client.auth.identity()?, - config.root_cert_client(), + config.cloud_root_certs(), ); assert_eq!(plugin.name, "test"); assert_eq!(plugin.path, dummy_plugin_path); @@ -92,7 +92,7 @@ mod tests { None, None, None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); let module = SoftwareModule { @@ -125,7 +125,7 @@ mod tests { None, None, None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); // Create test module with name `test2`. @@ -164,7 +164,7 @@ mod tests { None, None, None, - RootCertClient::from([]), + CloudRootCerts::from([]), ); // Create software module without an explicit type. diff --git a/crates/core/tedge_agent/src/agent.rs b/crates/core/tedge_agent/src/agent.rs index 71cf4ba4dee..656dd862c2c 100644 --- a/crates/core/tedge_agent/src/agent.rs +++ b/crates/core/tedge_agent/src/agent.rs @@ -51,7 +51,7 @@ use tedge_mqtt_ext::TopicFilter; use tedge_script_ext::ScriptActor; use tedge_signal_ext::SignalActor; use tedge_uploader_ext::UploaderActor; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tedge_utils::file::create_directory_with_defaults; use tracing::info; use tracing::instrument; @@ -79,7 +79,7 @@ pub(crate) struct AgentConfig { pub tedge_http_host: Arc, pub service: TEdgeConfigReaderService, pub identity: Option, - pub root_cert_client: RootCertClient, + pub cloud_root_certs: CloudRootCerts, pub fts_url: Arc, pub is_sudo_enabled: bool, pub capabilities: Capabilities, @@ -151,7 +151,7 @@ impl AgentConfig { let operations_dir = config_dir.join("operations"); let identity = tedge_config.http.client.auth.identity()?; - let root_cert_client = tedge_config.root_cert_client(); + let cloud_root_certs = tedge_config.cloud_root_certs(); let is_sudo_enabled = tedge_config.sudo.enable; @@ -184,7 +184,7 @@ impl AgentConfig { mqtt_device_topic_id, tedge_http_host, identity, - root_cert_client, + cloud_root_certs, fts_url, is_sudo_enabled, service: tedge_config.service.clone(), @@ -283,11 +283,11 @@ impl Agent { let mut fs_watch_actor_builder = FsWatchActorBuilder::new(); let mut downloader_actor_builder = DownloaderActor::new( self.config.identity.clone(), - self.config.root_cert_client.clone(), + self.config.cloud_root_certs.clone(), ) .builder(); let mut uploader_actor_builder = - UploaderActor::new(self.config.identity, self.config.root_cert_client).builder(); + UploaderActor::new(self.config.identity, self.config.cloud_root_certs).builder(); // Instantiate config manager actor if config_snapshot or both operations are enabled let config_actor_builder: Option = diff --git a/crates/core/tedge_mapper/src/c8y/mapper.rs b/crates/core/tedge_mapper/src/c8y/mapper.rs index 107a81ef1e0..a2acd65e5ce 100644 --- a/crates/core/tedge_mapper/src/c8y/mapper.rs +++ b/crates/core/tedge_mapper/src/c8y/mapper.rs @@ -201,10 +201,10 @@ impl TEdgeComponent for CumulocityMapper { let mut timer_actor = TimerActor::builder(); let identity = tedge_config.http.client.auth.identity()?; - let root_cert_client = tedge_config.root_cert_client(); + let cloud_root_certs = tedge_config.cloud_root_certs(); let mut uploader_actor = - UploaderActor::new(identity.clone(), root_cert_client.clone()).builder(); - let mut downloader_actor = DownloaderActor::new(identity, root_cert_client).builder(); + UploaderActor::new(identity.clone(), cloud_root_certs.clone()).builder(); + let mut downloader_actor = DownloaderActor::new(identity, cloud_root_certs).builder(); // MQTT client dedicated to monitor the c8y-bridge client status and also // set service down status on shutdown, using a last-will message. diff --git a/crates/extensions/c8y_auth_proxy/src/actor.rs b/crates/extensions/c8y_auth_proxy/src/actor.rs index 20720df20d7..7255b6c7171 100644 --- a/crates/extensions/c8y_auth_proxy/src/actor.rs +++ b/crates/extensions/c8y_auth_proxy/src/actor.rs @@ -41,7 +41,7 @@ impl C8yAuthProxyBuilder { config: &TEdgeConfig, jwt: &mut ServerActorBuilder, ) -> anyhow::Result { - let reqwest_client = config.root_cert_client().builder().build().unwrap(); + let reqwest_client = config.cloud_root_certs().client_builder().build().unwrap(); let app_data = AppData { is_https: true, host: config.c8y.http.or_config_not_set()?.to_string(), diff --git a/crates/extensions/c8y_http_proxy/src/actor.rs b/crates/extensions/c8y_http_proxy/src/actor.rs index 637612208e7..3257f3e150e 100644 --- a/crates/extensions/c8y_http_proxy/src/actor.rs +++ b/crates/extensions/c8y_http_proxy/src/actor.rs @@ -517,7 +517,7 @@ impl C8YHttpProxyActor { request.file_path, request.file_permissions, self.config.identity.clone(), - self.config.root_cert_client.clone(), + self.config.cloud_root_certs.clone(), ); downloader.download(&download_info).await?; diff --git a/crates/extensions/c8y_http_proxy/src/lib.rs b/crates/extensions/c8y_http_proxy/src/lib.rs index 4a12cecba1c..b8cec84de26 100644 --- a/crates/extensions/c8y_http_proxy/src/lib.rs +++ b/crates/extensions/c8y_http_proxy/src/lib.rs @@ -22,7 +22,7 @@ use tedge_config::ReadError; use tedge_config::TEdgeConfig; use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpResult; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; mod actor; pub mod credentials; @@ -40,7 +40,7 @@ pub struct C8YHttpConfig { pub device_id: String, pub tmp_dir: PathBuf, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, retry_interval: Duration, } @@ -53,7 +53,7 @@ impl TryFrom<&TEdgeConfig> for C8YHttpConfig { let device_id = tedge_config.device.id.try_read(tedge_config)?.to_string(); let tmp_dir = tedge_config.tmp.path.as_std_path().to_path_buf(); let identity = tedge_config.http.client.auth.identity()?; - let root_cert_client = tedge_config.root_cert_client(); + let cloud_root_certs = tedge_config.cloud_root_certs(); let retry_interval = Duration::from_secs(5); Ok(Self { @@ -62,7 +62,7 @@ impl TryFrom<&TEdgeConfig> for C8YHttpConfig { device_id, tmp_dir, identity, - root_cert_client, + cloud_root_certs, retry_interval, }) } diff --git a/crates/extensions/c8y_http_proxy/src/tests.rs b/crates/extensions/c8y_http_proxy/src/tests.rs index 288b12d5698..152b2d56cc5 100644 --- a/crates/extensions/c8y_http_proxy/src/tests.rs +++ b/crates/extensions/c8y_http_proxy/src/tests.rs @@ -29,7 +29,7 @@ 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 tedge_utils::certificates::CloudRootCerts; use time::macros::datetime; #[tokio::test] @@ -365,7 +365,7 @@ async fn retry_internal_id_on_expired_jwt_with_mock() { device_id: external_id.into(), tmp_dir: tmp_dir.into(), identity: None, - root_cert_client: RootCertClient::from([]), + cloud_root_certs: CloudRootCerts::from([]), retry_interval: Duration::from_millis(100), }; let c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http_actor, &mut jwt); @@ -434,7 +434,7 @@ async fn retry_create_event_on_expired_jwt_with_mock() { device_id: external_id.into(), tmp_dir: tmp_dir.into(), identity: None, - root_cert_client: RootCertClient::from([]), + cloud_root_certs: CloudRootCerts::from([]), retry_interval: Duration::from_millis(100), }; let c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http_actor, &mut jwt); @@ -679,7 +679,7 @@ async fn spawn_c8y_http_proxy( device_id, tmp_dir, identity: None, - root_cert_client: RootCertClient::from([]), + cloud_root_certs: CloudRootCerts::from([]), retry_interval: Duration::from_millis(10), }; let mut c8y_proxy_actor = C8YHttpProxyBuilder::new(config, &mut http, &mut jwt); diff --git a/crates/extensions/tedge_downloader_ext/src/actor.rs b/crates/extensions/tedge_downloader_ext/src/actor.rs index 6d6d52e8327..848a2ce2aeb 100644 --- a/crates/extensions/tedge_downloader_ext/src/actor.rs +++ b/crates/extensions/tedge_downloader_ext/src/actor.rs @@ -13,7 +13,7 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tedge_utils::file::PermissionEntry; #[derive(Debug, Clone, Eq, PartialEq)] @@ -71,7 +71,7 @@ pub struct DownloaderActor { config: ServerConfig, key: std::marker::PhantomData, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, } impl Clone for DownloaderActor { @@ -80,18 +80,18 @@ impl Clone for DownloaderActor { config: self.config, key: self.key, identity: self.identity.clone(), - root_cert_client: self.root_cert_client.clone(), + cloud_root_certs: self.cloud_root_certs.clone(), } } } impl DownloaderActor { - pub fn new(identity: Option, root_cert_client: RootCertClient) -> Self { + pub fn new(identity: Option, cloud_root_certs: CloudRootCerts) -> Self { DownloaderActor { config: <_>::default(), key: PhantomData, identity, - root_cert_client, + cloud_root_certs, } } @@ -99,17 +99,10 @@ impl DownloaderActor { ServerActorBuilder::new(self.clone(), &ServerConfig::new(), Sequential) } - pub fn with_capacity( - self, - capacity: usize, - identity: Option, - root_cert_client: RootCertClient, - ) -> Self { + pub fn with_capacity(self, capacity: usize) -> Self { Self { config: self.config.with_capacity(capacity), - key: self.key, - identity, - root_cert_client, + ..self } } } @@ -137,13 +130,13 @@ impl Server for DownloaderActor { request.file_path.clone(), permission, self.identity.clone(), - self.root_cert_client.clone(), + self.cloud_root_certs.clone(), ) } else { Downloader::new( request.file_path.clone(), self.identity.clone(), - self.root_cert_client.clone(), + self.cloud_root_certs.clone(), ) }; diff --git a/crates/extensions/tedge_downloader_ext/src/tests.rs b/crates/extensions/tedge_downloader_ext/src/tests.rs index b26f6dbc970..2020c823a3c 100644 --- a/crates/extensions/tedge_downloader_ext/src/tests.rs +++ b/crates/extensions/tedge_downloader_ext/src/tests.rs @@ -3,7 +3,7 @@ use download::Auth; use std::time::Duration; use tedge_actors::ClientMessageBox; use tedge_test_utils::fs::TempTedgeDir; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tedge_utils::file::PermissionEntry; use tokio::time::timeout; @@ -111,7 +111,7 @@ async fn download_with_permission() { async fn spawn_downloader_actor( ) -> ClientMessageBox<(String, DownloadRequest), (String, DownloadResult)> { let mut downloader_actor_builder = - DownloaderActor::new(None, RootCertClient::from([])).builder(); + DownloaderActor::new(None, CloudRootCerts::from([])).builder(); let requester = ClientMessageBox::new(&mut downloader_actor_builder); tokio::spawn(downloader_actor_builder.run()); @@ -162,7 +162,7 @@ struct TestDownloadKey { async fn spawn_downloader_actor_with_struct( ) -> ClientMessageBox<(TestDownloadKey, DownloadRequest), (TestDownloadKey, DownloadResult)> { let mut downloader_actor_builder = - DownloaderActor::new(None, RootCertClient::from([])).builder(); + DownloaderActor::new(None, CloudRootCerts::from([])).builder(); let requester = ClientMessageBox::new(&mut downloader_actor_builder); tokio::spawn(downloader_actor_builder.run()); diff --git a/crates/extensions/tedge_http_ext/Cargo.toml b/crates/extensions/tedge_http_ext/Cargo.toml index a83e2d47881..4f92d361057 100644 --- a/crates/extensions/tedge_http_ext/Cargo.toml +++ b/crates/extensions/tedge_http_ext/Cargo.toml @@ -18,7 +18,7 @@ test_helpers = [] async-trait = { workspace = true } futures = { workspace = true } http = { workspace = true } -hyper = { workspace = true, default_features = false, features = [ +hyper = { workspace = true, default-features = false, features = [ "client", "http1", "http2", @@ -31,7 +31,7 @@ serde_json = { workspace = true } tedge_actors = { workspace = true } tedge_config = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, default_features = false, features = [ +tokio = { workspace = true, default-features = false, features = [ "macros", "rt", ] } diff --git a/crates/extensions/tedge_http_ext/src/lib.rs b/crates/extensions/tedge_http_ext/src/lib.rs index 99c6b507bd4..c89293d9f16 100644 --- a/crates/extensions/tedge_http_ext/src/lib.rs +++ b/crates/extensions/tedge_http_ext/src/lib.rs @@ -37,17 +37,17 @@ impl HttpActor { ) } - pub fn with_capacity(self, capacity: usize, tedge_config: &TEdgeConfig) -> Self { + pub fn with_capacity(self, capacity: usize) -> Self { Self { config: self.config.with_capacity(capacity), - ..Self::new(tedge_config) + ..self } } - pub fn with_max_concurrency(self, max_concurrency: usize, tedge_config: &TEdgeConfig) -> Self { + pub fn with_max_concurrency(self, max_concurrency: usize) -> Self { Self { config: self.config.with_max_concurrency(max_concurrency), - ..Self::new(tedge_config) + ..self } } } diff --git a/crates/extensions/tedge_uploader_ext/src/actor.rs b/crates/extensions/tedge_uploader_ext/src/actor.rs index eca518af713..d887b1012b8 100644 --- a/crates/extensions/tedge_uploader_ext/src/actor.rs +++ b/crates/extensions/tedge_uploader_ext/src/actor.rs @@ -7,7 +7,7 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use upload::Auth; use upload::ContentType; use upload::UploadError; @@ -85,15 +85,15 @@ pub type UploadResult = Result; pub struct UploaderActor { config: ServerConfig, identity: Option, - root_cert_client: RootCertClient, + cloud_root_certs: CloudRootCerts, } impl UploaderActor { - pub fn new(identity: Option, root_cert_client: RootCertClient) -> Self { + pub fn new(identity: Option, cloud_root_certs: CloudRootCerts) -> Self { Self { config: ServerConfig::default(), identity, - root_cert_client, + cloud_root_certs, } } pub fn builder(self) -> ServerActorBuilder { @@ -131,7 +131,7 @@ impl Server for UploaderActor { let uploader = Uploader::new( request.file_path.clone(), self.identity.clone(), - self.root_cert_client.clone(), + self.cloud_root_certs.clone(), ); info!( diff --git a/crates/extensions/tedge_uploader_ext/src/tests.rs b/crates/extensions/tedge_uploader_ext/src/tests.rs index 41af6b50ca5..924f7c168a3 100644 --- a/crates/extensions/tedge_uploader_ext/src/tests.rs +++ b/crates/extensions/tedge_uploader_ext/src/tests.rs @@ -6,7 +6,7 @@ use camino::Utf8Path; use tedge_actors::ClientMessageBox; use tedge_actors::DynError; use tedge_test_utils::fs::TempTedgeDir; -use tedge_utils::certificates::RootCertClient; +use tedge_utils::certificates::CloudRootCerts; use tokio::time::timeout; use upload::Auth; @@ -20,7 +20,7 @@ async fn spawn_uploader_actor() -> ClientMessageBox<(String, UploadRequest), (St { let identity = None; let mut uploader_actor_builder = - UploaderActor::new(identity, RootCertClient::from([])).builder(); + UploaderActor::new(identity, CloudRootCerts::from([])).builder(); let requester = ClientMessageBox::new(&mut uploader_actor_builder); tokio::spawn(uploader_actor_builder.run()); diff --git a/plugins/c8y_firmware_plugin/src/lib.rs b/plugins/c8y_firmware_plugin/src/lib.rs index 67e4330747f..0f75d989785 100644 --- a/plugins/c8y_firmware_plugin/src/lib.rs +++ b/plugins/c8y_firmware_plugin/src/lib.rs @@ -85,8 +85,8 @@ async fn run_with(tedge_config: TEdgeConfig) -> Result<(), anyhow::Error> { tedge_config.c8y.bridge.topic_prefix.clone(), ); let identity = tedge_config.http.client.auth.identity()?; - let root_cert_client = tedge_config.root_cert_client(); - let mut downloader_actor = DownloaderActor::new(identity, root_cert_client).builder(); + let cloud_root_certs = tedge_config.cloud_root_certs(); + let mut downloader_actor = DownloaderActor::new(identity, cloud_root_certs).builder(); let mut mqtt_actor = MqttActorBuilder::new(mqtt_config.clone().with_session_name(PLUGIN_NAME)); //Instantiate health monitor actor From 716e3307cd803090639c98ac5d8deef72706ebda Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 23 Jul 2024 17:38:22 +0100 Subject: [PATCH 12/17] Move the certificate logic from tedge_utils to certificate crate --- Cargo.lock | 10 ++++-- clippy.toml | 9 ++++-- crates/common/certificate/Cargo.toml | 10 ++++-- .../src/cloud_root_certificate.rs} | 24 +++++++++++++- crates/common/certificate/src/lib.rs | 5 +++ crates/common/download/Cargo.toml | 1 + .../download/examples/simple_download.rs | 2 +- crates/common/download/src/download.rs | 2 +- crates/common/tedge_config/Cargo.toml | 2 +- .../src/tedge_config_cli/figment.rs | 31 +++++++++++++++---- .../src/tedge_config_cli/tedge_config.rs | 4 +-- crates/common/tedge_utils/Cargo.toml | 1 - crates/common/tedge_utils/src/lib.rs | 1 - crates/common/upload/Cargo.toml | 2 +- crates/common/upload/src/upload.rs | 2 +- crates/core/plugin_sm/Cargo.toml | 1 + crates/core/plugin_sm/src/plugin.rs | 2 +- crates/core/plugin_sm/tests/plugin.rs | 2 +- crates/core/tedge/Cargo.toml | 2 +- crates/core/tedge/src/cli/certificate/cli.rs | 2 +- .../core/tedge/src/cli/certificate/upload.rs | 24 ++++---------- crates/core/tedge_agent/Cargo.toml | 1 + crates/core/tedge_agent/src/agent.rs | 2 +- crates/extensions/c8y_auth_proxy/src/actor.rs | 2 +- crates/extensions/c8y_http_proxy/Cargo.toml | 1 + crates/extensions/c8y_http_proxy/src/lib.rs | 2 +- crates/extensions/c8y_http_proxy/src/tests.rs | 2 +- .../tedge_downloader_ext/Cargo.toml | 1 + .../tedge_downloader_ext/src/actor.rs | 2 +- .../tedge_downloader_ext/src/tests.rs | 2 +- .../extensions/tedge_uploader_ext/Cargo.toml | 1 + .../tedge_uploader_ext/src/actor.rs | 2 +- .../tedge_uploader_ext/src/tests.rs | 2 +- 33 files changed, 106 insertions(+), 53 deletions(-) rename crates/common/{tedge_utils/src/certificates.rs => certificate/src/cloud_root_certificate.rs} (73%) diff --git a/Cargo.lock b/Cargo.lock index 658ac6c4dab..afa43ecaa8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,6 +745,7 @@ dependencies = [ "anyhow", "async-trait", "c8y_api", + "certificate", "download", "http 0.2.11", "hyper 0.14.28", @@ -837,6 +838,7 @@ dependencies = [ "anyhow", "assert_matches", "base64 0.13.1", + "camino", "rcgen", "reqwest", "rustls 0.21.11", @@ -1252,6 +1254,7 @@ dependencies = [ "anyhow", "axum_tls", "backoff", + "certificate", "hyper 0.14.28", "log", "mockito", @@ -2639,6 +2642,7 @@ dependencies = [ "anyhow", "async-trait", "camino", + "certificate", "csv", "download", "regex", @@ -3732,6 +3736,7 @@ dependencies = [ "axum_tls", "bytes", "camino", + "certificate", "clap", "flockfile", "futures", @@ -4004,6 +4009,7 @@ version = "1.1.1" dependencies = [ "async-trait", "camino", + "certificate", "download", "log", "mockito", @@ -4179,6 +4185,7 @@ version = "1.1.1" dependencies = [ "async-trait", "camino", + "certificate", "log", "mockito", "reqwest", @@ -4204,7 +4211,6 @@ dependencies = [ "notify", "notify-debouncer-full", "once_cell", - "reqwest", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -4765,6 +4771,7 @@ dependencies = [ "axum_tls", "backoff", "camino", + "certificate", "futures", "log", "mime", @@ -4772,7 +4779,6 @@ dependencies = [ "mockito", "reqwest", "tedge_test_utils", - "tedge_utils", "tempfile", "thiserror", "tokio", diff --git a/clippy.toml b/clippy.toml index 9a05eb07870..1dc291a3919 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,9 +1,12 @@ disallowed-types = [ - { path = "reqwest::ClientBuilder", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" } + { path = "reqwest::ClientBuilder", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, + { path = "reqwest::blocking::ClientBuilder", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, ] disallowed-methods = [ - { path = "reqwest::Client::builder", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" }, - { path = "reqwest::Client::new", reason = "Use certificate::CloudRootCerts type instead to take root_cert_path configurations into account" }, + { path = "reqwest::Client::builder", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, + { path = "reqwest::blocking::Client::builder", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, + { path = "reqwest::blocking::Client::new", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, + { path = "reqwest::Client::new", reason = "Use `certificate::CloudRootCerts` type instead to take root_cert_path configurations into account" }, { path = "hyper::client::Client::new", reason = "Use Client::builder()" }, { path = "hyper_rustls::HttpsConnectorBuilder::with_native_roots", reason = "Use .with_tls_config(tedge_config.cloud_client_tls_config()) instead to use configured root certificate paths for the connected cloud" }, ] diff --git a/crates/common/certificate/Cargo.toml b/crates/common/certificate/Cargo.toml index 95c5ec47395..c17298dd2d1 100644 --- a/crates/common/certificate/Cargo.toml +++ b/crates/common/certificate/Cargo.toml @@ -8,9 +8,16 @@ license = { workspace = true } homepage = { workspace = true } repository = { workspace = true } +[features] +default = [] +reqwest-blocking = ["dep:reqwest", "reqwest/blocking"] +reqwest = ["dep:reqwest"] + [dependencies] +anyhow = { workspace = true } +camino = { workspace = true } rcgen = { workspace = true } -reqwest = { workspace = true } +reqwest = { workspace = true, optional = true } rustls = { workspace = true } rustls-native-certs = { workspace = true } rustls-pemfile = { workspace = true } @@ -22,7 +29,6 @@ x509-parser = { workspace = true } zeroize = { workspace = true } [dev-dependencies] -anyhow = { workspace = true } assert_matches = { workspace = true } base64 = { workspace = true } tempfile = { workspace = true } diff --git a/crates/common/tedge_utils/src/certificates.rs b/crates/common/certificate/src/cloud_root_certificate.rs similarity index 73% rename from crates/common/tedge_utils/src/certificates.rs rename to crates/common/certificate/src/cloud_root_certificate.rs index 923a7640df4..b8692dae760 100644 --- a/crates/common/tedge_utils/src/certificates.rs +++ b/crates/common/certificate/src/cloud_root_certificate.rs @@ -1,9 +1,9 @@ use anyhow::Context; use camino::Utf8Path; use camino::Utf8PathBuf; -use reqwest::Certificate; use std::fs::File; use std::sync::Arc; +use reqwest::Certificate; #[derive(Debug, Clone)] pub struct CloudRootCerts { @@ -20,6 +20,28 @@ impl CloudRootCerts { builder.add_root_certificate(cert) }) } + + #[allow(clippy::disallowed_types)] + pub fn client(&self) -> reqwest::Client { + self.client_builder().build().expect("Valid reqwest client builder configuration") + } + + #[allow(clippy::disallowed_types)] + #[cfg(feature = "reqwest-blocking")] + pub fn blocking_client_builder(&self) -> reqwest::blocking::ClientBuilder { + self.certificates + .iter() + .cloned() + .fold(reqwest::blocking::ClientBuilder::new(), |builder, cert| { + builder.add_root_certificate(cert) + }) + } + + #[allow(clippy::disallowed_types)] + #[cfg(feature = "reqwest-blocking")] + pub fn blocking_client(&self) -> reqwest::blocking::Client { + self.blocking_client_builder().build().expect("Valid reqwest client builder configuration") + } } impl From> for CloudRootCerts { diff --git a/crates/common/certificate/src/lib.rs b/crates/common/certificate/src/lib.rs index d4385f4d012..a991b21697d 100644 --- a/crates/common/certificate/src/lib.rs +++ b/crates/common/certificate/src/lib.rs @@ -9,6 +9,11 @@ use std::path::PathBuf; use time::Duration; use time::OffsetDateTime; use zeroize::Zeroizing; +#[cfg(feature = "reqwest")] +mod cloud_root_certificate; +#[cfg(feature = "reqwest")] +pub use cloud_root_certificate::*; + pub mod device_id; pub mod parse_root_certificate; pub struct PemCertificate { diff --git a/crates/common/download/Cargo.toml b/crates/common/download/Cargo.toml index 668f6355e43..020df9f561c 100644 --- a/crates/common/download/Cargo.toml +++ b/crates/common/download/Cargo.toml @@ -13,6 +13,7 @@ repository = { workspace = true } anyhow = { workspace = true, features = ["backtrace"] } axum_tls = { workspace = true, features = ["error-matching"] } backoff = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } hyper = { workspace = true } log = { workspace = true } nix = { workspace = true } diff --git a/crates/common/download/examples/simple_download.rs b/crates/common/download/examples/simple_download.rs index c460b9cfd3d..2c4738f7daf 100644 --- a/crates/common/download/examples/simple_download.rs +++ b/crates/common/download/examples/simple_download.rs @@ -1,7 +1,7 @@ use anyhow::Result; use download::DownloadInfo; use download::Downloader; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; /// This example shows how to use the `downloader`. #[tokio::main] diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 1c445169f68..223964fcef3 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -24,7 +24,7 @@ use std::os::unix::prelude::AsRawFd; use std::path::Path; use std::path::PathBuf; use std::time::Duration; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tedge_utils::file::move_file; use tedge_utils::file::FileError; use tedge_utils::file::PermissionEntry; diff --git a/crates/common/tedge_config/Cargo.toml b/crates/common/tedge_config/Cargo.toml index 7eded77bde6..d154008ac3b 100644 --- a/crates/common/tedge_config/Cargo.toml +++ b/crates/common/tedge_config/Cargo.toml @@ -15,7 +15,7 @@ test = [] [dependencies] anyhow = { workspace = true } camino = { workspace = true, features = ["serde", "serde1"] } -certificate = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } doku = { workspace = true } figment = { workspace = true, features = ["env", "toml"] } humantime = { workspace = true } diff --git a/crates/common/tedge_config/src/tedge_config_cli/figment.rs b/crates/common/tedge_config/src/tedge_config_cli/figment.rs index 49c48d720ec..a0db53e1f93 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/figment.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/figment.rs @@ -209,7 +209,7 @@ impl TEdgeEnv { fn provider(&self) -> figment::providers::Env { static WARNINGS: Lazy>> = Lazy::new(<_>::default); - figment::providers::Env::prefixed(self.prefix).map(move |name| { + figment::providers::Env::prefixed(self.prefix).ignore(&["CONFIG_DIR"]).map(move |name| { let lowercase_name = name.as_str().to_ascii_lowercase(); Uncased::new( tracing::subscriber::with_default( @@ -220,11 +220,11 @@ impl TEdgeEnv { .map_err(|err| { let is_read_only_key = matches!(err, crate::ParseKeyError::ReadOnly(_)); if is_read_only_key && !WARNINGS.lock().unwrap().insert(lowercase_name.clone()) { - tracing::error!( - "Failed to configure tedge with environment variable `TEDGE_{name}`: {}", - err.to_string().replace('\n', " ") - ) - } + tracing::error!( + "Failed to configure tedge with environment variable `TEDGE_{name}`: {}", + err.to_string().replace('\n', " ") + ) + } }) .unwrap_or(lowercase_name), ) @@ -240,6 +240,25 @@ mod tests { use super::*; + #[test] + fn config_dir_environment_variable_does_not_generate_a_warning() { + #[derive(Deserialize)] + struct Config {} + + figment::Jail::expect_with(|jail| { + jail.set_env("TEDGE_CONFIG_DIR", "/etc/moved-tedge"); + + let env = TEdgeEnv::default(); + let figment = Figment::new() + .merge(Toml::file("tedge.toml")) + .merge(env.provider()); + + let warnings = unused_value_warnings::(&figment, &env).unwrap(); + assert_eq!(dbg!(warnings).len(), 0); + + Ok(()) + }) + } #[test] fn environment_variables_override_config_file() { #[derive(Deserialize)] diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index 5b988aad66a..0c909dfb4b2 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -41,8 +41,8 @@ use tedge_config_macros::struct_field_aliases; use tedge_config_macros::struct_field_paths; pub use tedge_config_macros::ConfigNotSet; use tedge_config_macros::OptionalConfig; -use tedge_utils::certificates::read_trust_store; -use tedge_utils::certificates::CloudRootCerts; +use certificate::read_trust_store; +use certificate::CloudRootCerts; use toml::Table; use tracing::error; diff --git a/crates/common/tedge_utils/Cargo.toml b/crates/common/tedge_utils/Cargo.toml index b3115f19f0c..70a535657b2 100644 --- a/crates/common/tedge_utils/Cargo.toml +++ b/crates/common/tedge_utils/Cargo.toml @@ -25,7 +25,6 @@ mqtt_channel = { workspace = true } nix = { workspace = true } notify = { workspace = true, optional = true } notify-debouncer-full = { workspace = true, optional = true } -reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } rustls-pemfile = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } diff --git a/crates/common/tedge_utils/src/lib.rs b/crates/common/tedge_utils/src/lib.rs index d895fcb8350..a3e4acd1513 100644 --- a/crates/common/tedge_utils/src/lib.rs +++ b/crates/common/tedge_utils/src/lib.rs @@ -9,6 +9,5 @@ pub mod futures; #[cfg(feature = "fs-notify")] pub mod notify; -pub mod certificates; #[cfg(feature = "timestamp")] pub mod timestamp; diff --git a/crates/common/upload/Cargo.toml b/crates/common/upload/Cargo.toml index e027068bea0..1c50aac809f 100644 --- a/crates/common/upload/Cargo.toml +++ b/crates/common/upload/Cargo.toml @@ -13,6 +13,7 @@ repository = { workspace = true } axum_tls = { workspace = true, features = ["error-matching"] } backoff = { workspace = true } camino = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } log = { workspace = true } mime = { workspace = true } mime_guess = { workspace = true } @@ -21,7 +22,6 @@ reqwest = { workspace = true, features = [ "rustls-tls-native-roots", "multipart", ] } -tedge_utils = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["fs"] } tokio-util = { workspace = true, features = ["codec"] } diff --git a/crates/common/upload/src/upload.rs b/crates/common/upload/src/upload.rs index 204bc43f527..8e164d41437 100644 --- a/crates/common/upload/src/upload.rs +++ b/crates/common/upload/src/upload.rs @@ -13,7 +13,7 @@ use reqwest::multipart; use reqwest::Body; use reqwest::Identity; use std::time::Duration; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tokio::fs::File; use tokio_util::codec::BytesCodec; use tokio_util::codec::FramedRead; diff --git a/crates/core/plugin_sm/Cargo.toml b/crates/core/plugin_sm/Cargo.toml index 4755d8e8b2d..413a5a63eaf 100644 --- a/crates/core/plugin_sm/Cargo.toml +++ b/crates/core/plugin_sm/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } camino = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } csv = { workspace = true } download = { workspace = true } regex = { workspace = true } diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index 45bf3a9f49b..ed58d8c4da3 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -17,7 +17,7 @@ use tedge_api::SoftwareModuleUpdate; use tedge_api::SoftwareType; use tedge_api::DEFAULT; use tedge_config::SudoCommandBuilder; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tokio::io::AsyncWriteExt; use tracing::error; diff --git a/crates/core/plugin_sm/tests/plugin.rs b/crates/core/plugin_sm/tests/plugin.rs index b6353cc36e3..10e6ad78a9c 100644 --- a/crates/core/plugin_sm/tests/plugin.rs +++ b/crates/core/plugin_sm/tests/plugin.rs @@ -13,7 +13,7 @@ mod tests { use tedge_api::SoftwareModule; use tedge_config::SudoCommandBuilder; use tedge_config::TEdgeConfigLocation; - use tedge_utils::certificates::CloudRootCerts; + use certificate::CloudRootCerts; use test_case::test_case; #[test_case("abc", Some("1.0") ; "with version")] diff --git a/crates/core/tedge/Cargo.toml b/crates/core/tedge/Cargo.toml index ab3b7a2ba65..aecef0879c0 100644 --- a/crates/core/tedge/Cargo.toml +++ b/crates/core/tedge/Cargo.toml @@ -18,7 +18,7 @@ c8y-firmware-plugin = { workspace = true } c8y-remote-access-plugin = { workspace = true } camino = { workspace = true } cap = { workspace = true } -certificate = { workspace = true } +certificate = { workspace = true, features = ["reqwest-blocking"] } clap = { workspace = true, features = [ "cargo", "derive", diff --git a/crates/core/tedge/src/cli/certificate/cli.rs b/crates/core/tedge/src/cli/certificate/cli.rs index e7778ed4d6b..ebabda86784 100644 --- a/crates/core/tedge/src/cli/certificate/cli.rs +++ b/crates/core/tedge/src/cli/certificate/cli.rs @@ -102,7 +102,7 @@ impl BuildCommand for TEdgeCertCli { device_id: config.device.id.try_read(&config)?.clone(), path: config.device.cert_path.clone(), host: config.c8y.http.or_err()?.to_owned(), - root_cert_path: config.c8y.root_cert_path.clone(), + cloud_root_certs: config.cloud_root_certs(), username, password, }, diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs index 2dfee44e67e..cb28379f794 100644 --- a/crates/core/tedge/src/cli/certificate/upload.rs +++ b/crates/core/tedge/src/cli/certificate/upload.rs @@ -4,12 +4,11 @@ use crate::command::Command; use camino::Utf8PathBuf; use reqwest::StatusCode; use reqwest::Url; +use certificate::CloudRootCerts; use std::io::prelude::*; -use std::io::ErrorKind; use std::path::Path; use tedge_config::HostPort; use tedge_config::HTTPS_PORT; -use tedge_utils::certificates::read_trust_store; #[derive(Debug, serde::Deserialize)] struct CumulocityResponse { @@ -30,7 +29,7 @@ pub struct UploadCertCmd { pub path: Utf8PathBuf, pub host: HostPort, pub username: String, - pub root_cert_path: Utf8PathBuf, + pub cloud_root_certs: CloudRootCerts, pub password: String, } @@ -70,21 +69,7 @@ impl UploadCertCmd { self.password.clone() }; - let mut client_builder = reqwest::blocking::Client::builder(); - if let Err(e) = std::fs::metadata(&self.root_cert_path) { - let e = match e.kind() { - ErrorKind::NotFound => { - CertError::RootCertificatePathDoesNotExist(self.root_cert_path.to_string()) - } - _ => CertError::IoError(e), - }; - return Err(e); - } - - for certificate in read_trust_store(&self.root_cert_path)? { - client_builder = client_builder.add_root_certificate(certificate); - } - let client = client_builder.build()?; + let client = self.cloud_root_certs.blocking_client(); // To post certificate c8y requires one of the following endpoints: // https://.cumulocity.url.io[:port]/tenant/tenants//trusted-certificates @@ -196,6 +181,7 @@ mod tests { } #[test] + #[allow(clippy::disallowed_methods)] fn get_tenant_id_blocking_should_return_error_given_wrong_credentials() { let client = reqwest::blocking::Client::new(); @@ -220,6 +206,7 @@ mod tests { } #[test] + #[allow(clippy::disallowed_methods)] fn get_tenant_id_blocking_returns_correct_response() { let client = reqwest::blocking::Client::new(); @@ -247,6 +234,7 @@ mod tests { } #[test] + #[allow(clippy::disallowed_methods)] fn get_tenant_id_blocking_response_should_return_error_when_response_has_no_name_field() { let client = reqwest::blocking::Client::new(); diff --git a/crates/core/tedge_agent/Cargo.toml b/crates/core/tedge_agent/Cargo.toml index 2a8ba314a98..cbdb4b6ae03 100644 --- a/crates/core/tedge_agent/Cargo.toml +++ b/crates/core/tedge_agent/Cargo.toml @@ -16,6 +16,7 @@ axum = { workspace = true } axum-server = { workspace = true } axum_tls = { workspace = true } camino = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } clap = { workspace = true } flockfile = { workspace = true } futures = { workspace = true } diff --git a/crates/core/tedge_agent/src/agent.rs b/crates/core/tedge_agent/src/agent.rs index 656dd862c2c..5bb9b390d95 100644 --- a/crates/core/tedge_agent/src/agent.rs +++ b/crates/core/tedge_agent/src/agent.rs @@ -15,6 +15,7 @@ use crate::Capabilities; use anyhow::Context; use camino::Utf8Path; use camino::Utf8PathBuf; +use certificate::CloudRootCerts; use flockfile::check_another_instance_is_not_running; use flockfile::Flockfile; use flockfile::FlockfileError; @@ -51,7 +52,6 @@ use tedge_mqtt_ext::TopicFilter; use tedge_script_ext::ScriptActor; use tedge_signal_ext::SignalActor; use tedge_uploader_ext::UploaderActor; -use tedge_utils::certificates::CloudRootCerts; use tedge_utils::file::create_directory_with_defaults; use tracing::info; use tracing::instrument; diff --git a/crates/extensions/c8y_auth_proxy/src/actor.rs b/crates/extensions/c8y_auth_proxy/src/actor.rs index 7255b6c7171..3683860ce88 100644 --- a/crates/extensions/c8y_auth_proxy/src/actor.rs +++ b/crates/extensions/c8y_auth_proxy/src/actor.rs @@ -41,7 +41,7 @@ impl C8yAuthProxyBuilder { config: &TEdgeConfig, jwt: &mut ServerActorBuilder, ) -> anyhow::Result { - let reqwest_client = config.cloud_root_certs().client_builder().build().unwrap(); + let reqwest_client = config.cloud_root_certs().client(); let app_data = AppData { is_https: true, host: config.c8y.http.or_config_not_set()?.to_string(), diff --git a/crates/extensions/c8y_http_proxy/Cargo.toml b/crates/extensions/c8y_http_proxy/Cargo.toml index f1f4405795f..ca26ba78de6 100644 --- a/crates/extensions/c8y_http_proxy/Cargo.toml +++ b/crates/extensions/c8y_http_proxy/Cargo.toml @@ -13,6 +13,7 @@ repository = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } c8y_api = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } download = { workspace = true } http = { workspace = true } hyper = { workspace = true } diff --git a/crates/extensions/c8y_http_proxy/src/lib.rs b/crates/extensions/c8y_http_proxy/src/lib.rs index b8cec84de26..49f90edb98d 100644 --- a/crates/extensions/c8y_http_proxy/src/lib.rs +++ b/crates/extensions/c8y_http_proxy/src/lib.rs @@ -4,6 +4,7 @@ use crate::credentials::JwtResult; use crate::credentials::JwtRetriever; use crate::messages::C8YRestRequest; use crate::messages::C8YRestResult; +use certificate::CloudRootCerts; use reqwest::Identity; use std::convert::Infallible; use std::path::PathBuf; @@ -22,7 +23,6 @@ use tedge_config::ReadError; use tedge_config::TEdgeConfig; use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpResult; -use tedge_utils::certificates::CloudRootCerts; mod actor; pub mod credentials; diff --git a/crates/extensions/c8y_http_proxy/src/tests.rs b/crates/extensions/c8y_http_proxy/src/tests.rs index 152b2d56cc5..d34ba86043f 100644 --- a/crates/extensions/c8y_http_proxy/src/tests.rs +++ b/crates/extensions/c8y_http_proxy/src/tests.rs @@ -29,7 +29,7 @@ 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::CloudRootCerts; +use certificate::CloudRootCerts; use time::macros::datetime; #[tokio::test] diff --git a/crates/extensions/tedge_downloader_ext/Cargo.toml b/crates/extensions/tedge_downloader_ext/Cargo.toml index ad6429265d2..bc558c65dff 100644 --- a/crates/extensions/tedge_downloader_ext/Cargo.toml +++ b/crates/extensions/tedge_downloader_ext/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } [dependencies] async-trait = { workspace = true } camino = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } download = { workspace = true } log = { workspace = true } reqwest = { workspace = true } diff --git a/crates/extensions/tedge_downloader_ext/src/actor.rs b/crates/extensions/tedge_downloader_ext/src/actor.rs index 848a2ce2aeb..5569449cf00 100644 --- a/crates/extensions/tedge_downloader_ext/src/actor.rs +++ b/crates/extensions/tedge_downloader_ext/src/actor.rs @@ -13,7 +13,7 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tedge_utils::file::PermissionEntry; #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/crates/extensions/tedge_downloader_ext/src/tests.rs b/crates/extensions/tedge_downloader_ext/src/tests.rs index 2020c823a3c..7a7d10f67c5 100644 --- a/crates/extensions/tedge_downloader_ext/src/tests.rs +++ b/crates/extensions/tedge_downloader_ext/src/tests.rs @@ -3,7 +3,7 @@ use download::Auth; use std::time::Duration; use tedge_actors::ClientMessageBox; use tedge_test_utils::fs::TempTedgeDir; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tedge_utils::file::PermissionEntry; use tokio::time::timeout; diff --git a/crates/extensions/tedge_uploader_ext/Cargo.toml b/crates/extensions/tedge_uploader_ext/Cargo.toml index ec31daa7d0e..f2b74dd6ad1 100644 --- a/crates/extensions/tedge_uploader_ext/Cargo.toml +++ b/crates/extensions/tedge_uploader_ext/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } [dependencies] async-trait = { workspace = true } camino = { workspace = true } +certificate = { workspace = true, features = ["reqwest"] } log = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } tedge_actors = { workspace = true } diff --git a/crates/extensions/tedge_uploader_ext/src/actor.rs b/crates/extensions/tedge_uploader_ext/src/actor.rs index d887b1012b8..7f8a92358b1 100644 --- a/crates/extensions/tedge_uploader_ext/src/actor.rs +++ b/crates/extensions/tedge_uploader_ext/src/actor.rs @@ -7,7 +7,7 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use upload::Auth; use upload::ContentType; use upload::UploadError; diff --git a/crates/extensions/tedge_uploader_ext/src/tests.rs b/crates/extensions/tedge_uploader_ext/src/tests.rs index 924f7c168a3..f2462cd4673 100644 --- a/crates/extensions/tedge_uploader_ext/src/tests.rs +++ b/crates/extensions/tedge_uploader_ext/src/tests.rs @@ -6,7 +6,7 @@ use camino::Utf8Path; use tedge_actors::ClientMessageBox; use tedge_actors::DynError; use tedge_test_utils::fs::TempTedgeDir; -use tedge_utils::certificates::CloudRootCerts; +use certificate::CloudRootCerts; use tokio::time::timeout; use upload::Auth; From 587a4b73dcc8f89b041d378934a3b392abe438fd Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Tue, 23 Jul 2024 17:38:56 +0100 Subject: [PATCH 13/17] Run formatter --- .../common/certificate/src/cloud_root_certificate.rs | 10 +++++++--- crates/common/download/examples/simple_download.rs | 2 +- crates/common/download/src/download.rs | 2 +- .../tedge_config/src/tedge_config_cli/tedge_config.rs | 4 ++-- crates/common/upload/src/upload.rs | 2 +- crates/core/plugin_sm/src/plugin.rs | 2 +- crates/core/plugin_sm/tests/plugin.rs | 2 +- crates/core/tedge/src/cli/certificate/upload.rs | 2 +- crates/extensions/c8y_http_proxy/src/tests.rs | 2 +- crates/extensions/tedge_downloader_ext/src/actor.rs | 2 +- crates/extensions/tedge_downloader_ext/src/tests.rs | 2 +- crates/extensions/tedge_uploader_ext/src/actor.rs | 2 +- crates/extensions/tedge_uploader_ext/src/tests.rs | 2 +- 13 files changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/common/certificate/src/cloud_root_certificate.rs b/crates/common/certificate/src/cloud_root_certificate.rs index b8692dae760..ff43bf84eda 100644 --- a/crates/common/certificate/src/cloud_root_certificate.rs +++ b/crates/common/certificate/src/cloud_root_certificate.rs @@ -1,9 +1,9 @@ use anyhow::Context; use camino::Utf8Path; use camino::Utf8PathBuf; +use reqwest::Certificate; use std::fs::File; use std::sync::Arc; -use reqwest::Certificate; #[derive(Debug, Clone)] pub struct CloudRootCerts { @@ -23,7 +23,9 @@ impl CloudRootCerts { #[allow(clippy::disallowed_types)] pub fn client(&self) -> reqwest::Client { - self.client_builder().build().expect("Valid reqwest client builder configuration") + self.client_builder() + .build() + .expect("Valid reqwest client builder configuration") } #[allow(clippy::disallowed_types)] @@ -40,7 +42,9 @@ impl CloudRootCerts { #[allow(clippy::disallowed_types)] #[cfg(feature = "reqwest-blocking")] pub fn blocking_client(&self) -> reqwest::blocking::Client { - self.blocking_client_builder().build().expect("Valid reqwest client builder configuration") + self.blocking_client_builder() + .build() + .expect("Valid reqwest client builder configuration") } } diff --git a/crates/common/download/examples/simple_download.rs b/crates/common/download/examples/simple_download.rs index 2c4738f7daf..809e4a42296 100644 --- a/crates/common/download/examples/simple_download.rs +++ b/crates/common/download/examples/simple_download.rs @@ -1,7 +1,7 @@ use anyhow::Result; +use certificate::CloudRootCerts; use download::DownloadInfo; use download::Downloader; -use certificate::CloudRootCerts; /// This example shows how to use the `downloader`. #[tokio::main] diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 223964fcef3..0d9ff1c6537 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -4,6 +4,7 @@ use crate::error::ErrContext; use anyhow::anyhow; use backoff::future::retry_notify; use backoff::ExponentialBackoff; +use certificate::CloudRootCerts; use log::debug; use log::info; use log::warn; @@ -24,7 +25,6 @@ use std::os::unix::prelude::AsRawFd; use std::path::Path; use std::path::PathBuf; use std::time::Duration; -use certificate::CloudRootCerts; use tedge_utils::file::move_file; use tedge_utils::file::FileError; use tedge_utils::file::PermissionEntry; diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index 0c909dfb4b2..241b4453fa4 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -16,7 +16,9 @@ use anyhow::Context; use camino::Utf8PathBuf; use certificate::parse_root_certificate::client_config_for_ca_certificates; use certificate::parse_root_certificate::create_tls_config; +use certificate::read_trust_store; use certificate::CertificateError; +use certificate::CloudRootCerts; use certificate::PemCertificate; use doku::Document; use doku::Type; @@ -41,8 +43,6 @@ use tedge_config_macros::struct_field_aliases; use tedge_config_macros::struct_field_paths; pub use tedge_config_macros::ConfigNotSet; use tedge_config_macros::OptionalConfig; -use certificate::read_trust_store; -use certificate::CloudRootCerts; use toml::Table; use tracing::error; diff --git a/crates/common/upload/src/upload.rs b/crates/common/upload/src/upload.rs index 8e164d41437..be1083dc947 100644 --- a/crates/common/upload/src/upload.rs +++ b/crates/common/upload/src/upload.rs @@ -3,6 +3,7 @@ use backoff::future::retry_notify; use backoff::ExponentialBackoff; use camino::Utf8Path; use camino::Utf8PathBuf; +use certificate::CloudRootCerts; use log::info; use log::warn; use mime::Mime; @@ -13,7 +14,6 @@ use reqwest::multipart; use reqwest::Body; use reqwest::Identity; use std::time::Duration; -use certificate::CloudRootCerts; use tokio::fs::File; use tokio_util::codec::BytesCodec; use tokio_util::codec::FramedRead; diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index ed58d8c4da3..10d1a7cc8dc 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use certificate::CloudRootCerts; use csv::ReaderBuilder; use download::Downloader; use regex::Regex; @@ -17,7 +18,6 @@ use tedge_api::SoftwareModuleUpdate; use tedge_api::SoftwareType; use tedge_api::DEFAULT; use tedge_config::SudoCommandBuilder; -use certificate::CloudRootCerts; use tokio::io::AsyncWriteExt; use tracing::error; diff --git a/crates/core/plugin_sm/tests/plugin.rs b/crates/core/plugin_sm/tests/plugin.rs index 10e6ad78a9c..1a5936db274 100644 --- a/crates/core/plugin_sm/tests/plugin.rs +++ b/crates/core/plugin_sm/tests/plugin.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests { + use certificate::CloudRootCerts; use plugin_sm::plugin::deserialize_module_info; use plugin_sm::plugin::sm_path; use plugin_sm::plugin::ExternalPluginCommand; @@ -13,7 +14,6 @@ mod tests { use tedge_api::SoftwareModule; use tedge_config::SudoCommandBuilder; use tedge_config::TEdgeConfigLocation; - use certificate::CloudRootCerts; use test_case::test_case; #[test_case("abc", Some("1.0") ; "with version")] diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs index cb28379f794..88664ef42fe 100644 --- a/crates/core/tedge/src/cli/certificate/upload.rs +++ b/crates/core/tedge/src/cli/certificate/upload.rs @@ -2,9 +2,9 @@ use super::error::get_webpki_error_from_reqwest; use super::error::CertError; use crate::command::Command; use camino::Utf8PathBuf; +use certificate::CloudRootCerts; use reqwest::StatusCode; use reqwest::Url; -use certificate::CloudRootCerts; use std::io::prelude::*; use std::path::Path; use tedge_config::HostPort; diff --git a/crates/extensions/c8y_http_proxy/src/tests.rs b/crates/extensions/c8y_http_proxy/src/tests.rs index d34ba86043f..a1a6fa5f56c 100644 --- a/crates/extensions/c8y_http_proxy/src/tests.rs +++ b/crates/extensions/c8y_http_proxy/src/tests.rs @@ -9,6 +9,7 @@ use async_trait::async_trait; use c8y_api::json_c8y::C8yEventResponse; use c8y_api::json_c8y::C8yUpdateSoftwareListResponse; use c8y_api::json_c8y::InternalIdResponse; +use certificate::CloudRootCerts; use http::StatusCode; use mockito::Matcher; use std::collections::HashMap; @@ -29,7 +30,6 @@ use tedge_http_ext::HttpRequest; use tedge_http_ext::HttpRequestBuilder; use tedge_http_ext::HttpResult; use tedge_test_utils::fs::TempTedgeDir; -use certificate::CloudRootCerts; use time::macros::datetime; #[tokio::test] diff --git a/crates/extensions/tedge_downloader_ext/src/actor.rs b/crates/extensions/tedge_downloader_ext/src/actor.rs index 5569449cf00..ae0ed005061 100644 --- a/crates/extensions/tedge_downloader_ext/src/actor.rs +++ b/crates/extensions/tedge_downloader_ext/src/actor.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use certificate::CloudRootCerts; use download::Auth; use download::DownloadError; use download::DownloadInfo; @@ -13,7 +14,6 @@ use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use certificate::CloudRootCerts; use tedge_utils::file::PermissionEntry; #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/crates/extensions/tedge_downloader_ext/src/tests.rs b/crates/extensions/tedge_downloader_ext/src/tests.rs index 7a7d10f67c5..e0d6b5de124 100644 --- a/crates/extensions/tedge_downloader_ext/src/tests.rs +++ b/crates/extensions/tedge_downloader_ext/src/tests.rs @@ -1,9 +1,9 @@ use super::*; +use certificate::CloudRootCerts; use download::Auth; use std::time::Duration; use tedge_actors::ClientMessageBox; use tedge_test_utils::fs::TempTedgeDir; -use certificate::CloudRootCerts; use tedge_utils::file::PermissionEntry; use tokio::time::timeout; diff --git a/crates/extensions/tedge_uploader_ext/src/actor.rs b/crates/extensions/tedge_uploader_ext/src/actor.rs index 7f8a92358b1..6b78243629e 100644 --- a/crates/extensions/tedge_uploader_ext/src/actor.rs +++ b/crates/extensions/tedge_uploader_ext/src/actor.rs @@ -1,13 +1,13 @@ use async_trait::async_trait; use camino::Utf8Path; use camino::Utf8PathBuf; +use certificate::CloudRootCerts; use log::info; use reqwest::Identity; use tedge_actors::Sequential; use tedge_actors::Server; use tedge_actors::ServerActorBuilder; use tedge_actors::ServerConfig; -use certificate::CloudRootCerts; use upload::Auth; use upload::ContentType; use upload::UploadError; diff --git a/crates/extensions/tedge_uploader_ext/src/tests.rs b/crates/extensions/tedge_uploader_ext/src/tests.rs index f2462cd4673..c556519f956 100644 --- a/crates/extensions/tedge_uploader_ext/src/tests.rs +++ b/crates/extensions/tedge_uploader_ext/src/tests.rs @@ -3,10 +3,10 @@ use std::io::Write; use std::time::Duration; use camino::Utf8Path; +use certificate::CloudRootCerts; use tedge_actors::ClientMessageBox; use tedge_actors::DynError; use tedge_test_utils::fs::TempTedgeDir; -use certificate::CloudRootCerts; use tokio::time::timeout; use upload::Auth; From 3445e4470613361c578a4ccdf6ac6e91cc340a8d Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 24 Jul 2024 10:41:20 +0100 Subject: [PATCH 14/17] Fix error message --- Cargo.lock | 3 --- crates/common/certificate/src/cloud_root_certificate.rs | 9 +++++++-- .../tedge_config/src/tedge_config_cli/tedge_config.rs | 6 +++--- crates/common/tedge_utils/Cargo.toml | 2 -- crates/extensions/c8y_auth_proxy/Cargo.toml | 1 - tests/RobotFramework/tests/tedge/tedge_upload_cert.robot | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afa43ecaa8d..48eaa6fc313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -704,7 +704,6 @@ dependencies = [ "tedge_config", "tedge_config_macros", "tedge_http_ext", - "tedge_utils", "tokio", "tokio-tungstenite", "tracing", @@ -4202,7 +4201,6 @@ version = "1.1.1" dependencies = [ "anyhow", "assert_matches", - "camino", "doku", "futures", "maplit", @@ -4211,7 +4209,6 @@ dependencies = [ "notify", "notify-debouncer-full", "once_cell", - "rustls-pemfile 1.0.4", "serde", "serde_json", "strum", diff --git a/crates/common/certificate/src/cloud_root_certificate.rs b/crates/common/certificate/src/cloud_root_certificate.rs index ff43bf84eda..c423addb8a1 100644 --- a/crates/common/certificate/src/cloud_root_certificate.rs +++ b/crates/common/certificate/src/cloud_root_certificate.rs @@ -73,9 +73,14 @@ pub fn read_trust_store(ca_dir_or_file: &Utf8Path) -> anyhow::Result pem_file, + err if path == ca_dir_or_file => { + err.with_context(|| format!("failed to read from path {path:?}"))? + } + Err(_other_unreadable_file) => continue, }; + let ders = rustls_pemfile::certs(&mut pem_file) .with_context(|| format!("reading {path}"))? .into_iter() diff --git a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs index 241b4453fa4..64407269e7e 100644 --- a/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs +++ b/crates/common/tedge_config/src/tedge_config_cli/tedge_config.rs @@ -973,21 +973,21 @@ impl TEdgeConfigReader { let roots = CLOUD_ROOT_CERTIFICATES.get_or_init(|| { let c8y_roots = read_trust_store(&self.c8y.root_cert_path).unwrap_or_else(move |e| { error!( - "Unable to read certificates from {}: {e}", + "Unable to read certificates from {}: {e:?}", ReadableKey::C8yRootCertPath ); vec![] }); let az_roots = read_trust_store(&self.az.root_cert_path).unwrap_or_else(move |e| { error!( - "Unable to read certificates from {}: {e}", + "Unable to read certificates from {}: {e:?}", ReadableKey::AzRootCertPath ); vec![] }); let aws_roots = read_trust_store(&self.aws.root_cert_path).unwrap_or_else(move |e| { error!( - "Unable to read certificates from {}: {e}", + "Unable to read certificates from {}: {e:?}", ReadableKey::AwsRootCertPath ); vec![] diff --git a/crates/common/tedge_utils/Cargo.toml b/crates/common/tedge_utils/Cargo.toml index 70a535657b2..e4e5a0f94e5 100644 --- a/crates/common/tedge_utils/Cargo.toml +++ b/crates/common/tedge_utils/Cargo.toml @@ -18,14 +18,12 @@ timestamp = ["strum", "time", "serde", "serde_json"] [dependencies] anyhow = { workspace = true } -camino = { workspace = true } doku = { workspace = true } futures = { workspace = true } mqtt_channel = { workspace = true } nix = { workspace = true } notify = { workspace = true, optional = true } notify-debouncer-full = { workspace = true, optional = true } -rustls-pemfile = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } strum = { workspace = true, optional = true, features = ["derive"] } diff --git a/crates/extensions/c8y_auth_proxy/Cargo.toml b/crates/extensions/c8y_auth_proxy/Cargo.toml index d85b3503102..34c40a2d89a 100644 --- a/crates/extensions/c8y_auth_proxy/Cargo.toml +++ b/crates/extensions/c8y_auth_proxy/Cargo.toml @@ -23,7 +23,6 @@ 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/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot b/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot index 30a3c0bbef2..a9dd792d000 100644 --- a/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot +++ b/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot @@ -63,7 +63,7 @@ tedge cert upload c8y command fails Execute Command tedge config set c8y.root_cert_path /etc/ssl/certs_test ${output}= Execute Command sudo env C8YPASS\='password' tedge cert upload c8y --user testuser ignore_exit_code=${True} stdout=${False} stderr=${True} Execute Command tedge config unset c8y.root_cert_path - Should Contain ${output} Root certificate path /etc/ssl/certs_test does not exist + Should Contain ${output} Unable to read certificates from c8y.root_cert_path: failed to read from path "/etc/ssl/certs_test" *** Keywords *** Setup With Self-Signed Certificate From 3daae979a2719df24effbe492bd2e82f7dc6e79b Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 24 Jul 2024 10:41:32 +0100 Subject: [PATCH 15/17] Simplify test as suggested --- .../test_remote_access_custom_root_cert_path.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot index 513ad6b3f20..815998d415e 100644 --- a/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot +++ b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot @@ -12,8 +12,8 @@ Test Teardown Get Logs Execute ssh command with a custom root certificate path ${KEY_FILE}= Configure SSH Add Remote Access Passthrough Configuration - ${stdout}= Execute Remote Access Command command=tedge --version exp_exit_code=0 user=root key_file=${KEY_FILE} - Should Match Regexp ${stdout} tedge .+ + ${stdout}= Execute Remote Access Command command=sh -c 'echo foobar' exp_exit_code=0 user=root key_file=${KEY_FILE} + Should Contain ${stdout} foobar *** Keywords *** From b61ddbc0c80a2a343fa5127036301f0051a83cf6 Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 24 Jul 2024 11:00:28 +0100 Subject: [PATCH 16/17] Replace unwrap with expect --- crates/common/download/src/download.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/common/download/src/download.rs b/crates/common/download/src/download.rs index 0d9ff1c6537..a9108fc34fd 100644 --- a/crates/common/download/src/download.rs +++ b/crates/common/download/src/download.rs @@ -123,8 +123,7 @@ impl Downloader { if let Some(identity) = identity { client_builder = client_builder.identity(identity); } - // TODO don't unwrap - let client = client_builder.build().unwrap(); + let client = client_builder.build().expect("Client builder is valid"); Self { target_filename: target_path, target_permission: PermissionEntry::default(), @@ -145,8 +144,7 @@ impl Downloader { if let Some(identity) = identity { client_builder = client_builder.identity(identity); } - // TODO no unwrap - let client = client_builder.build().unwrap(); + let client = client_builder.build().expect("Client builder is valid"); Self { target_filename: target_path, target_permission, From e637b251896b1ff07689a71623d0ae16a6aae30c Mon Sep 17 00:00:00 2001 From: James Rhodes Date: Wed, 24 Jul 2024 12:33:40 +0100 Subject: [PATCH 17/17] Fix system test --- .../test_remote_access_custom_root_cert_path.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot index 815998d415e..9ea788e1376 100644 --- a/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot +++ b/tests/RobotFramework/tests/cumulocity/remote-access/test_remote_access_custom_root_cert_path.robot @@ -12,7 +12,7 @@ Test Teardown Get Logs Execute ssh command with a custom root certificate path ${KEY_FILE}= Configure SSH Add Remote Access Passthrough Configuration - ${stdout}= Execute Remote Access Command command=sh -c 'echo foobar' exp_exit_code=0 user=root key_file=${KEY_FILE} + ${stdout}= Execute Remote Access Command command=echo foobar exp_exit_code=0 user=root key_file=${KEY_FILE} Should Contain ${stdout} foobar *** Keywords ***