Skip to content

Commit

Permalink
Merge pull request #2961 from reubenmiller/feat-cert-upload-c8y-username
Browse files Browse the repository at this point in the history
feat: tedge cert upload usability improvements
  • Loading branch information
reubenmiller authored Jul 1, 2024
2 parents cf109f3 + 60a40f4 commit e6c7635
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 14 deletions.
1 change: 1 addition & 0 deletions crates/core/tedge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ certificate = { workspace = true }
clap = { workspace = true, features = [
"cargo",
"derive",
"env",
"string",
"unstable-styles",
] }
Expand Down
22 changes: 19 additions & 3 deletions crates/core/tedge/src/cli/certificate/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
},
}
35 changes: 28 additions & 7 deletions crates/core/tedge/src/cli/certificate/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct UploadCertCmd {
pub path: Utf8PathBuf,
pub host: HostPort<HTTPS_PORT>,
pub username: String,
pub password: String,
}

impl Command for UploadCertCmd {
Expand All @@ -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())?;
Expand Down Expand Up @@ -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)?;
Expand All @@ -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)?;

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
robotframework-devicelibrary[docker] @ git+https://github.com/reubenmiller/[email protected].0
robotframework-devicelibrary[docker] @ git+https://github.com/reubenmiller/[email protected].1
Original file line number Diff line number Diff line change
@@ -1 +1 @@
robotframework-devicelibrary[local] @ git+https://github.com/reubenmiller/[email protected].0
robotframework-devicelibrary[local] @ git+https://github.com/reubenmiller/[email protected].1
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
robotframework-devicelibrary[ssh] @ git+https://github.com/reubenmiller/[email protected].0
robotframework-devicelibrary[ssh] @ git+https://github.com/reubenmiller/[email protected].1
robotframework-sshlibrary~=3.8.0
20 changes: 19 additions & 1 deletion tests/RobotFramework/tests/tedge/tedge_upload_cert.robot
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e6c7635

Please sign in to comment.