diff --git a/crates/core/tedge/Cargo.toml b/crates/core/tedge/Cargo.toml index 56a807261ad..ab3b7a2ba65 100644 --- a/crates/core/tedge/Cargo.toml +++ b/crates/core/tedge/Cargo.toml @@ -22,6 +22,7 @@ certificate = { workspace = true } clap = { workspace = true, features = [ "cargo", "derive", + "env", "string", "unstable-styles", ] } diff --git a/crates/core/tedge/src/cli/certificate/cli.rs b/crates/core/tedge/src/cli/certificate/cli.rs index ba0049292d2..9a33603878b 100644 --- a/crates/core/tedge/src/cli/certificate/cli.rs +++ b/crates/core/tedge/src/cli/certificate/cli.rs @@ -98,11 +98,12 @@ impl BuildCommand for TEdgeCertCli { TEdgeCertCli::Upload(cmd) => { let cmd = match cmd { - UploadCertCli::C8y { username } => UploadCertCmd { + UploadCertCli::C8y { username, password } => UploadCertCmd { device_id: config.device.id.try_read(&config)?.clone(), path: config.device.cert_path.clone(), host: config.c8y.http.or_err()?.to_owned(), username, + password, }, }; cmd.into_boxed() @@ -127,8 +128,23 @@ pub enum UploadCertCli { /// The command will upload root certificate to Cumulocity. C8y { #[clap(long = "user")] - /// Provided username should be a Cumulocity user with tenant management permissions. - /// The password is requested on /dev/tty, unless the $C8YPASS env var is set to the user password. + #[arg( + env = "C8Y_USER", + hide_env_values = true, + hide_default_value = true, + default_value = "" + )] + /// Provided username should be a Cumulocity IoT user with tenant management permissions. + /// You will be prompted for input if the value is not provided or is empty username: String, + + #[clap(long = "password")] + #[arg(env = "C8Y_PASSWORD", hide_env_values = true, hide_default_value = true, default_value_t = std::env::var("C8YPASS").unwrap_or_default().to_string())] + // Note: Prefer C8Y_PASSWORD over the now deprecated C8YPASS env variable as the former is also supported by other tooling such as go-c8y-cli + /// Cumulocity IoT Password. + /// You will be prompted for input if the value is not provided or is empty + /// + /// Notes: `C8YPASS` is deprecated. Please use the `C8Y_PASSWORD` env variable instead + password: String, }, } diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs index a44e17226a8..ff3e52f84bc 100644 --- a/crates/core/tedge/src/cli/certificate/upload.rs +++ b/crates/core/tedge/src/cli/certificate/upload.rs @@ -30,6 +30,7 @@ pub struct UploadCertCmd { pub path: Utf8PathBuf, pub host: HostPort, pub username: String, + pub password: String, } impl Command for UploadCertCmd { @@ -44,11 +45,30 @@ impl Command for UploadCertCmd { impl UploadCertCmd { fn upload_certificate(&self) -> Result<(), CertError> { + if std::env::var("C8YPASS").is_ok() { + eprintln!("WARN: Detected use of a deprecated env variable, C8YPASS. Please use C8Y_PASSWORD instead\n"); + } + + // Prompt if not already set + let username = if self.username.is_empty() { + print!("Enter username: "); + std::io::stdout().flush()?; + let mut input = String::new(); + std::io::stdin() + .read_line(&mut input) + .expect("Invalid username"); + input + .trim_end_matches(|c| c == '\n' || c == '\r') + .to_string() + } else { + self.username.clone() + }; + // Read the password from /dev/tty - // Unless a password is provided using the `C8YPASS` env var. - let password = match std::env::var("C8YPASS") { - Ok(password) => password, - Err(_) => rpassword::read_password_from_tty(Some("Enter password: "))?, + let password = if self.password.is_empty() { + rpassword::read_password_from_tty(Some("Enter password: "))? + } else { + self.password.clone() }; let config = TEdgeConfig::try_new(TEdgeConfigLocation::default())?; @@ -83,16 +103,17 @@ impl UploadCertCmd { let tenant_id = get_tenant_id_blocking( &client, build_get_tenant_id_url(&self.host.to_string())?, - &self.username, + &username, &password, )?; - self.post_certificate(&client, &tenant_id, &password) + self.post_certificate(&client, &tenant_id, &username, &password) } fn post_certificate( &self, client: &reqwest::blocking::Client, tenant_id: &str, + username: &str, password: &str, ) -> Result<(), CertError> { let post_url = build_upload_certificate_url(&self.host.to_string(), tenant_id)?; @@ -107,7 +128,7 @@ impl UploadCertCmd { let res = client .post(post_url) .json(&post_body) - .basic_auth(&self.username, Some(password)) + .basic_auth(username, Some(password)) .send() .map_err(get_webpki_error_from_reqwest)?; diff --git a/tests/RobotFramework/requirements/requirements.adapter-docker.txt b/tests/RobotFramework/requirements/requirements.adapter-docker.txt index c0b1185bd22..bce7e685d55 100644 --- a/tests/RobotFramework/requirements/requirements.adapter-docker.txt +++ b/tests/RobotFramework/requirements/requirements.adapter-docker.txt @@ -1 +1 @@ -robotframework-devicelibrary[docker] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.0 +robotframework-devicelibrary[docker] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.1 diff --git a/tests/RobotFramework/requirements/requirements.adapter-local.txt b/tests/RobotFramework/requirements/requirements.adapter-local.txt index ed66e19ea06..a8a96e0b242 100644 --- a/tests/RobotFramework/requirements/requirements.adapter-local.txt +++ b/tests/RobotFramework/requirements/requirements.adapter-local.txt @@ -1 +1 @@ -robotframework-devicelibrary[local] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.0 +robotframework-devicelibrary[local] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.1 diff --git a/tests/RobotFramework/requirements/requirements.adapter-ssh.txt b/tests/RobotFramework/requirements/requirements.adapter-ssh.txt index 19ab83a5718..0f5b97bacbb 100644 --- a/tests/RobotFramework/requirements/requirements.adapter-ssh.txt +++ b/tests/RobotFramework/requirements/requirements.adapter-ssh.txt @@ -1,2 +1,2 @@ -robotframework-devicelibrary[ssh] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.0 +robotframework-devicelibrary[ssh] @ git+https://github.com/reubenmiller/robotframework-devicelibrary.git@1.15.1 robotframework-sshlibrary~=3.8.0 diff --git a/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot b/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot index 9668a4a0011..30a3c0bbef2 100644 --- a/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot +++ b/tests/RobotFramework/tests/tedge/tedge_upload_cert.robot @@ -25,10 +25,28 @@ Renew the 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 sudo env C8YPASS\='${C8Y_CONFIG.password}' tedge cert upload c8y --user ${C8Y_CONFIG.username} + Execute Command sudo env C8YPASS\='${C8Y_CONFIG.password}' tedge cert upload c8y --user ${C8Y_CONFIG.username} log_output=${False} ${output}= Execute Command sudo tedge connect c8y Should Contain ${output} Connection check is successful. +Cert upload prompts for username (from stdin) + # Note: Use bash process substitution to simulate user input from /dev/stdin + [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 cmd=sudo env --unset=C8Y_USER C8Y_PASSWORD='${C8Y_CONFIG.password}' bash -c "tedge cert upload c8y < <(echo '${C8Y_CONFIG.username}')" log_output=${False} + ${output}= Execute Command sudo tedge connect c8y + Should Contain ${output} Connection check is successful. + +Cert upload supports reading username/password from go-c8y-cli env variables + [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 cmd=sudo env C8Y_USER='${C8Y_CONFIG.username}' C8Y_PASSWORD='${C8Y_CONFIG.password}' tedge cert upload c8y log_output=${False} + ${output}= Execute Command sudo tedge connect c8y + Should Contain ${output} Connection check is successful. Renew certificate fails [Setup] Setup Without Certificate