From 69441781857a3bac05469df319093172fa3e8ed0 Mon Sep 17 00:00:00 2001 From: Tobias Langer Date: Tue, 10 Jun 2025 12:05:28 +0200 Subject: [PATCH 1/2] fix(ssh): failure create https connection to keycloak The omnect-cli is not able to create ssh connections, as it cannot create secure connections to the keycloak server to retrieve an access token. This regression was introduced when default features were disabled for all dependencies. One consequence of this action was, that reqwest did no longer ship with tls support. We therefore re-enable said feature. --- Cargo.lock | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6ffbc3..968407c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -870,6 +870,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1645,6 +1655,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.11" @@ -2134,6 +2160,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -2215,7 +2258,7 @@ dependencies = [ [[package]] name = "omnect-cli" -version = "0.26.2" +version = "0.26.3" dependencies = [ "actix-web", "anyhow", @@ -2318,6 +2361,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "openssl-sys" version = "0.9.107" @@ -2757,19 +2806,23 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.6.0", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tower", "tower-service", "url", @@ -2834,6 +2887,21 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + [[package]] name = "rustversion" version = "1.0.20" @@ -2855,12 +2923,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.26" @@ -3134,6 +3234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand 2.3.0", + "getrandom 0.3.2", "once_cell", "rustix 1.0.5", "windows-sys 0.59.0", @@ -3276,6 +3377,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.14" diff --git a/Cargo.toml b/Cargo.toml index 2f50883..844d882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" name = "omnect-cli" readme = "README.md" repository = "https://github.com/omnect/omnect-cli" -version = "0.26.2" +version = "0.26.3" [dependencies] actix-web = "4.11" @@ -47,7 +47,7 @@ num_cpus = { version = "1.17", default-features = false } oauth2 = { version = "5.0", default-features = false, features = ["reqwest"] } open = { version = "5.3", default-features = false } regex = { version = "1.11", default-features = false } -reqwest = { version = "0.12", default-features = false, features = ["json"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "native-tls"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false } serde_path_to_error = { version = "0.1", default-features = false } From 3250029f0ab283146539b202f45c1af0d8650e87 Mon Sep 17 00:00:00 2001 From: Tobias Langer Date: Tue, 10 Jun 2025 12:14:39 +0200 Subject: [PATCH 2/2] style: cargo-fmt fixes --- src/auth.rs | 5 +- src/device_update.rs | 2 +- src/file/mod.rs | 8 ++- src/image.rs | 2 +- src/lib.rs | 38 +++++----- src/ssh.rs | 17 +++-- src/validators/identity.rs | 28 ++++---- tests/common/mod.rs | 4 +- tests/integration_tests.rs | 143 ++++++++++++++++++++++--------------- 9 files changed, 132 insertions(+), 115 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index f35f20f..b8bb436 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -4,13 +4,12 @@ use tokio::sync::{mpsc, oneshot}; use anyhow::Result; -use actix_web::{error, get, web, App, HttpServer}; +use actix_web::{App, HttpServer, error, get, web}; use serde::Deserialize; -use oauth2::basic::BasicClient; use oauth2::{ AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge, RedirectUrl, TokenResponse, - TokenUrl, + TokenUrl, basic::BasicClient, }; #[derive(Deserialize)] diff --git a/src/device_update.rs b/src/device_update.rs index 861f3ac..c59559a 100644 --- a/src/device_update.rs +++ b/src/device_update.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use azure_identity::{ClientSecretCredential, TokenCredentialOptions}; use azure_iot_deviceupdate::DeviceUpdateClient; -use azure_storage::{shared_access_signature::service_sas::BlobSasPermissions, StorageCredentials}; +use azure_storage::{StorageCredentials, shared_access_signature::service_sas::BlobSasPermissions}; use azure_storage_blobs::prelude::{BlobServiceClient, ContainerClient}; use base64::prelude::*; use log::{debug, info}; diff --git a/src/file/mod.rs b/src/file/mod.rs index 8013b66..a493e51 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -2,15 +2,17 @@ pub mod compression; pub mod functions; use super::validators::{ device_update, - identity::{validate_identity, IdentityConfig, IdentityType}, + identity::{IdentityConfig, IdentityType, validate_identity}, ssh::validate_ssh_pub_key, }; use crate::file::functions::{FileCopyFromParams, FileCopyToParams, Partition}; use anyhow::{Context, Result}; use log::warn; use regex::Regex; -use std::fs; -use std::path::{Path, PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; pub fn set_iotedge_gateway_config( config_file: &Path, diff --git a/src/image.rs b/src/image.rs index 6983322..b07222e 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,7 +1,7 @@ use std::path::Path; -use crate::file::functions::read_file_from_image; use crate::file::functions::Partition; +use crate::file::functions::read_file_from_image; use anyhow::{Context, Result}; use regex::Regex; use std::sync::LazyLock; diff --git a/src/lib.rs b/src/lib.rs index 11eeaa6..8713377 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,17 +251,14 @@ pub fn run() -> Result<()> { generate_bmap, compress_image, }) => { - let cert_info = create_image_cert( - &image, - CertificateOptions { - intermediate_full_chain_cert: &intermediate_full_chain_cert, - intermediate_key: &intermediate_key, - target_cert: "device_cert_path.pem", - target_key: "device_key_path.key.pem", - subject: &device_id, - validity_days: days, - }, - ) + let cert_info = create_image_cert(&image, CertificateOptions { + intermediate_full_chain_cert: &intermediate_full_chain_cert, + intermediate_key: &intermediate_key, + target_cert: "device_cert_path.pem", + target_key: "device_key_path.key.pem", + subject: &device_id, + validity_days: days, + }) .context("set_device_certificate: could not create certificate")?; run_image_command(image, generate_bmap, compress_image, |img| { @@ -282,17 +279,14 @@ pub fn run() -> Result<()> { generate_bmap, compress_image, }) => { - let cert_info = create_image_cert( - &image, - CertificateOptions { - intermediate_full_chain_cert: &intermediate_full_chain_cert, - intermediate_key: &intermediate_key, - target_cert: "edge_ca_cert_path.pem", - target_key: "edge_ca_key_path.key.pem", - subject: &device_id, - validity_days: days, - }, - ) + let cert_info = create_image_cert(&image, CertificateOptions { + intermediate_full_chain_cert: &intermediate_full_chain_cert, + intermediate_key: &intermediate_key, + target_cert: "edge_ca_cert_path.pem", + target_key: "edge_ca_key_path.key.pem", + subject: &device_id, + validity_days: days, + }) .context("set_edge_ca_certificate: could not create certificate")?; run_image_command(image, generate_bmap, compress_image, |img| { diff --git a/src/ssh.rs b/src/ssh.rs index 5cb7a88..f6f12f9 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -1,17 +1,14 @@ use std::convert::AsRef; use std::fs; -use std::io::prelude::*; -use std::io::BufWriter; +use std::io::{BufWriter, prelude::*}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str; -use anyhow::anyhow; -use anyhow::{Context, Result}; +use anyhow::{Context, Result, anyhow}; use directories::ProjectDirs; use oauth2::AccessToken; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use url::Url; static BACKEND_API_ENDPOINT: &str = "/api/devices/prepareSSHConnection"; @@ -192,9 +189,11 @@ async fn unpack_response(response: reqwest::Response) -> Re internal_message: String, } - anyhow::bail!(serde_json::from_str::(&body) - .map(|err| err.internal_message) - .unwrap_or_else(|_| "unknown error type".to_string())) + anyhow::bail!( + serde_json::from_str::(&body) + .map(|err| err.internal_message) + .unwrap_or_else(|_| "unknown error type".to_string()) + ) } else { serde_json::from_str(&body).map_err(|_| anyhow!("unsupported reply.")) } diff --git a/src/validators/identity.rs b/src/validators/identity.rs index 606eb6f..afc2c51 100644 --- a/src/validators/identity.rs +++ b/src/validators/identity.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use log::debug; use regex::Regex; use serde::Deserialize; @@ -189,19 +189,15 @@ const PAYLOAD_FILEPATH: &str = "file:///etc/omnect/dps-payload.json"; const WARN_MISSING_PROVISIONING: &str = "A provisioning section should be specified."; const WARN_MISSING_DPS_PARAMS: &str = "For provisioning source dps, global_endpoint and id_scope should be specified."; -const WARN_MISSING_MANUAL_PARAMS: &str = - "For provisioning source manual, either connection_string or iothub_hostname and device_id are required."; -const WARN_MISSING_AUTHENTICATION: &str = - "For provisioning source manual, an authentication section should be present in the provisioning section."; -const WARN_MISSING_ATTESTATION: &str = - "For provisioning source dps an attestation section should be present in the provisioning section."; +const WARN_MISSING_MANUAL_PARAMS: &str = "For provisioning source manual, either connection_string or iothub_hostname and device_id are required."; +const WARN_MISSING_AUTHENTICATION: &str = "For provisioning source manual, an authentication section should be present in the provisioning section."; +const WARN_MISSING_ATTESTATION: &str = "For provisioning source dps an attestation section should be present in the provisioning section."; const WARN_ATTESTATION_VALID_METHOD_EXPECTED: &str = "The attestation method should be tpm, x509 or symmetric_key."; const WARN_INVALID_SOURCE: &str = "The provisioning source should be dps or manual."; const WARN_AUTHENTICATION_VALID_METHOD_EXPECTED: &str = "The authentication method should be sas."; const WARN_UNEXPECTED_PATH: &str = "Unexpected path found."; -const WARN_UNEQUAL_COMMON_NAME_AND_REGISTRATION_ID: &str = - "provisioning.attestation.registration_id is not equal to provisioning.attestation.identity_cert.common_name"; +const WARN_UNEQUAL_COMMON_NAME_AND_REGISTRATION_ID: &str = "provisioning.attestation.registration_id is not equal to provisioning.attestation.identity_cert.common_name"; const WARN_PAYLOAD_FILEPATH_MISSING: &str = "Payload file is configred but file is missing."; const WARN_PAYLOAD_CONFIG_MISSING: &str = "Payload file is passed but not configred."; @@ -375,12 +371,14 @@ mod tests { #[test] fn identity_config_hostname_valid() { LazyLock::force(&LOG); - assert!(validate_identity( - IdentityType::Standalone, - Path::new("testfiles/identity_config_hostname_valid.toml"), - &None, - ) - .is_ok()); + assert!( + validate_identity( + IdentityType::Standalone, + Path::new("testfiles/identity_config_hostname_valid.toml"), + &None, + ) + .is_ok() + ); } #[test] diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 348f5a9..13e96a7 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,9 +1,7 @@ use data_encoding::HEXUPPER; use env_logger::{Builder, Env}; use ring::digest::{Context, SHA256}; -use std::fs::copy; -use std::fs::File; -use std::fs::{create_dir_all, remove_dir_all}; +use std::fs::{File, copy, create_dir_all, remove_dir_all}; use std::io::{BufReader, Read}; use std::path::PathBuf; use std::sync::LazyLock; diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3409d1f..4b9c492 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -108,17 +108,23 @@ fn check_set_identity_gateway_config() { edge_device_identity_key_file_path.to_str().unwrap(), edge_device_identity_key_file_out_path )); - assert!(std::path::Path::new(hosts_file_out_path) - .try_exists() - .is_ok_and(|exists| exists)); + assert!( + std::path::Path::new(hosts_file_out_path) + .try_exists() + .is_ok_and(|exists| exists) + ); - assert!(std::fs::read_to_string(hosts_file_out_path) - .unwrap() - .contains("127.0.1.1 my-omnect-iotedge-gateway-device")); + assert!( + std::fs::read_to_string(hosts_file_out_path) + .unwrap() + .contains("127.0.1.1 my-omnect-iotedge-gateway-device") + ); - assert!(std::fs::read_to_string(hostname_file_out_path) - .unwrap() - .contains("my-omnect-iotedge-gateway-device")); + assert!( + std::fs::read_to_string(hostname_file_out_path) + .unwrap() + .contains("my-omnect-iotedge-gateway-device") + ); } #[test] @@ -185,13 +191,17 @@ fn check_set_identity_leaf_config() { root_ca_file_out_path )); - assert!(std::fs::read_to_string(hosts_file_out_path) - .unwrap() - .contains("127.0.1.1 my-omnect-iot-leaf-device")); + assert!( + std::fs::read_to_string(hosts_file_out_path) + .unwrap() + .contains("127.0.1.1 my-omnect-iot-leaf-device") + ); - assert!(std::fs::read_to_string(hostname_file_out_path) - .unwrap() - .contains("my-omnect-iot-leaf-device")); + assert!( + std::fs::read_to_string(hostname_file_out_path) + .unwrap() + .contains("my-omnect-iot-leaf-device") + ); } #[test] @@ -247,13 +257,17 @@ fn check_set_identity_config_est_template() { config_file_out_path )); - assert!(std::fs::read_to_string(hosts_file_out_path) - .unwrap() - .contains("127.0.1.1 test-omnect-est")); + assert!( + std::fs::read_to_string(hosts_file_out_path) + .unwrap() + .contains("127.0.1.1 test-omnect-est") + ); - assert!(std::fs::read_to_string(hostname_file_out_path) - .unwrap() - .contains("test-omnect-est")); + assert!( + std::fs::read_to_string(hostname_file_out_path) + .unwrap() + .contains("test-omnect-est") + ); } #[test] @@ -323,13 +337,17 @@ fn check_set_identity_config_payload_template() { payload_out_path )); - assert!(std::fs::read_to_string(hosts_file_out_path) - .unwrap() - .contains("127.0.1.1 test-omnect-est-with-payload")); + assert!( + std::fs::read_to_string(hosts_file_out_path) + .unwrap() + .contains("127.0.1.1 test-omnect-est-with-payload") + ); - assert!(std::fs::read_to_string(hostname_file_out_path) - .unwrap() - .contains("test-omnect-est-with-payload")); + assert!( + std::fs::read_to_string(hostname_file_out_path) + .unwrap() + .contains("test-omnect-est-with-payload") + ); } #[test] @@ -385,13 +403,17 @@ fn check_set_identity_config_tpm_template() { config_file_out_path )); - assert!(std::fs::read_to_string(hosts_file_out_path) - .unwrap() - .contains("127.0.1.1 test-omnect-tpm")); + assert!( + std::fs::read_to_string(hosts_file_out_path) + .unwrap() + .contains("127.0.1.1 test-omnect-tpm") + ); - assert!(std::fs::read_to_string(hostname_file_out_path) - .unwrap() - .contains("test-omnect-tpm")); + assert!( + std::fs::read_to_string(hostname_file_out_path) + .unwrap() + .contains("test-omnect-tpm") + ); } #[test] @@ -1009,31 +1031,36 @@ async fn check_ssh_tunnel_setup() { .await .unwrap(); - assert!(tr - .pathbuf() - .join("config") - .try_exists() - .is_ok_and(|exists| exists)); - assert!(tr - .pathbuf() - .join("id_ed25519") - .try_exists() - .is_ok_and(|exists| exists)); - assert!(tr - .pathbuf() - .join("id_ed25519.pub") - .try_exists() - .is_ok_and(|exists| exists)); - assert!(tr - .pathbuf() - .join("bastion-cert.pub") - .try_exists() - .is_ok_and(|exists| exists)); - assert!(tr - .pathbuf() - .join("device-cert.pub") - .try_exists() - .is_ok_and(|exists| exists)); + assert!( + tr.pathbuf() + .join("config") + .try_exists() + .is_ok_and(|exists| exists) + ); + assert!( + tr.pathbuf() + .join("id_ed25519") + .try_exists() + .is_ok_and(|exists| exists) + ); + assert!( + tr.pathbuf() + .join("id_ed25519.pub") + .try_exists() + .is_ok_and(|exists| exists) + ); + assert!( + tr.pathbuf() + .join("bastion-cert.pub") + .try_exists() + .is_ok_and(|exists| exists) + ); + assert!( + tr.pathbuf() + .join("device-cert.pub") + .try_exists() + .is_ok_and(|exists| exists) + ); let ssh_config = std::fs::read_to_string(tr.pathbuf().join("config")).unwrap(); let expected_config = format!(