summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge/src/cli
diff options
context:
space:
mode:
authorLukasz Woznicki <75632179+makr11st@users.noreply.github.com>2021-11-24 20:54:56 +0000
committerGitHub <noreply@github.com>2021-11-24 20:54:56 +0000
commita4ffeccf60090e4456755bc53a6e3b8c8038e855 (patch)
tree9583f187114913a92866571920dd3bb205bd50a3 /crates/core/tedge/src/cli
parent8217e80670e76dbf9168780f5e0545355a39f8f3 (diff)
Restructure directories of the workspace (#559)
* Restructure directories of the workspace * Rename c8y_translator_lib to c8y_translator * Update comment on how to get dummy plugin path Signed-off-by: Lukasz Woznicki <lukasz.woznicki@softwareag.com>
Diffstat (limited to 'crates/core/tedge/src/cli')
-rw-r--r--crates/core/tedge/src/cli/certificate/cli.rs87
-rw-r--r--crates/core/tedge/src/cli/certificate/create.rs188
-rw-r--r--crates/core/tedge/src/cli/certificate/error.rs172
-rw-r--r--crates/core/tedge/src/cli/certificate/mod.rs8
-rw-r--r--crates/core/tedge/src/cli/certificate/remove.rs38
-rw-r--r--crates/core/tedge/src/cli/certificate/show.rs41
-rw-r--r--crates/core/tedge/src/cli/certificate/upload.rs251
-rw-r--r--crates/core/tedge/src/cli/config/cli.rs73
-rw-r--r--crates/core/tedge/src/cli/config/commands/get.rs39
-rw-r--r--crates/core/tedge/src/cli/config/commands/list.rs60
-rw-r--r--crates/core/tedge/src/cli/config/commands/mod.rs6
-rw-r--r--crates/core/tedge/src/cli/config/commands/set.rs25
-rw-r--r--crates/core/tedge/src/cli/config/commands/unset.rs24
-rw-r--r--crates/core/tedge/src/cli/config/config_key.rs76
-rw-r--r--crates/core/tedge/src/cli/config/mod.rs5
-rw-r--r--crates/core/tedge/src/cli/connect/bridge_config.rs369
-rw-r--r--crates/core/tedge/src/cli/connect/bridge_config_azure.rs111
-rw-r--r--crates/core/tedge/src/cli/connect/bridge_config_c8y.rs144
-rw-r--r--crates/core/tedge/src/cli/connect/cli.rs48
-rw-r--r--crates/core/tedge/src/cli/connect/command.rs569
-rw-r--r--crates/core/tedge/src/cli/connect/common_mosquitto_config.rs248
-rw-r--r--crates/core/tedge/src/cli/connect/error.rs41
-rw-r--r--crates/core/tedge/src/cli/connect/mod.rs12
-rw-r--r--crates/core/tedge/src/cli/disconnect/cli.rs38
-rw-r--r--crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs136
-rw-r--r--crates/core/tedge/src/cli/disconnect/error.rs19
-rw-r--r--crates/core/tedge/src/cli/disconnect/mod.rs5
-rw-r--r--crates/core/tedge/src/cli/mod.rs50
-rw-r--r--crates/core/tedge/src/cli/mqtt/cli.rs114
-rw-r--r--crates/core/tedge/src/cli/mqtt/error.rs17
-rw-r--r--crates/core/tedge/src/cli/mqtt/mod.rs7
-rw-r--r--crates/core/tedge/src/cli/mqtt/publish.rs90
-rw-r--r--crates/core/tedge/src/cli/mqtt/subscribe.rs81
33 files changed, 3192 insertions, 0 deletions
diff --git a/crates/core/tedge/src/cli/certificate/cli.rs b/crates/core/tedge/src/cli/certificate/cli.rs
new file mode 100644
index 00000000..7ef78fb5
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/cli.rs
@@ -0,0 +1,87 @@
+use super::{create::CreateCertCmd, remove::RemoveCertCmd, show::ShowCertCmd, upload::*};
+
+use crate::command::{BuildCommand, BuildContext, Command};
+use crate::ConfigError;
+
+use structopt::StructOpt;
+use tedge_config::*;
+
+#[derive(StructOpt, Debug)]
+pub enum TEdgeCertCli {
+ /// Create a self-signed device certificate
+ Create {
+ /// The device identifier to be used as the common name for the certificate
+ #[structopt(long = "device-id")]
+ id: String,
+ },
+
+ /// Show the device certificate, if any
+ Show,
+
+ /// Remove the device certificate
+ Remove,
+
+ /// Upload root certificate
+ Upload(UploadCertCli),
+}
+
+impl BuildCommand for TEdgeCertCli {
+ fn build_command(self, context: BuildContext) -> Result<Box<dyn Command>, ConfigError> {
+ let config = context.config_repository.load()?;
+
+ let cmd = match self {
+ TEdgeCertCli::Create { id } => {
+ let cmd = CreateCertCmd {
+ id,
+ cert_path: config.query(DeviceCertPathSetting)?,
+ key_path: config.query(DeviceKeyPathSetting)?,
+ user_manager: context.user_manager,
+ };
+ cmd.into_boxed()
+ }
+
+ TEdgeCertCli::Show => {
+ let cmd = ShowCertCmd {
+ cert_path: config.query(DeviceCertPathSetting)?,
+ };
+ cmd.into_boxed()
+ }
+
+ TEdgeCertCli::Remove => {
+ let cmd = RemoveCertCmd {
+ cert_path: config.query(DeviceCertPathSetting)?,
+ key_path: config.query(DeviceKeyPathSetting)?,
+ user_manager: context.user_manager,
+ };
+ cmd.into_boxed()
+ }
+
+ TEdgeCertCli::Upload(cmd) => {
+ let cmd = match cmd {
+ UploadCertCli::C8y { username } => UploadCertCmd {
+ device_id: config.query(DeviceIdSetting)?,
+ path: config.query(DeviceCertPathSetting)?,
+ host: config.query(C8yUrlSetting)?,
+ username,
+ },
+ };
+ cmd.into_boxed()
+ }
+ };
+
+ Ok(cmd)
+ }
+}
+
+#[derive(StructOpt, Debug)]
+pub enum UploadCertCli {
+ /// Upload root certificate to Cumulocity
+ ///
+ /// The command will upload root certificate to Cumulocity.
+ C8y {
+ #[structopt(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.
+ username: String,
+ },
+}
diff --git a/crates/core/tedge/src/cli/certificate/create.rs b/crates/core/tedge/src/cli/certificate/create.rs
new file mode 100644
index 00000000..560c2a8f
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/create.rs
@@ -0,0 +1,188 @@
+use super::error::CertError;
+use crate::command::Command;
+use certificate::{KeyCertPair, NewCertificateConfig};
+use std::{
+ fs::{File, OpenOptions},
+ io::prelude::*,
+ path::Path,
+};
+use tedge_config::*;
+use tedge_users::UserManager;
+use tedge_utils::paths::{set_permission, validate_parent_dir_exists};
+
+/// Create a self-signed device certificate
+pub struct CreateCertCmd {
+ /// The device identifier
+ pub id: String,
+
+ /// The path where the device certificate will be stored
+ pub cert_path: FilePath,
+
+ /// The path where the device private key will be stored
+ pub key_path: FilePath,
+
+ /// The UserManager required to change effective user id
+ pub user_manager: UserManager,
+}
+
+impl Command for CreateCertCmd {
+ fn description(&self) -> String {
+ format!("create a test certificate for the device {}.", self.id)
+ }
+
+ fn execute(&self) -> anyhow::Result<()> {
+ let config = NewCertificateConfig::default();
+ let () = self.create_test_certificate(&config)?;
+ Ok(())
+ }
+}
+
+impl CreateCertCmd {
+ fn create_test_certificate(&self, config: &NewCertificateConfig) -> Result<(), CertError> {
+ let _user_guard = self.user_manager.become_user(tedge_users::BROKER_USER)?;
+
+ validate_parent_dir_exists(&self.cert_path).map_err(CertError::CertPathError)?;
+ validate_parent_dir_exists(&self.key_path).map_err(CertError::KeyPathError)?;
+
+ let cert = KeyCertPair::new_selfsigned_certificate(config, &self.id)?;
+
+ // Creating files with permission 644
+ let mut cert_file = create_new_file(&self.cert_path)
+ .map_err(|err| err.cert_context(self.cert_path.clone()))?;
+ let mut key_file = create_new_file(&self.key_path)
+ .map_err(|err| err.key_context(self.key_path.clone()))?;
+
+ let cert_pem = cert.certificate_pem_string()?;
+ cert_file.write_all(cert_pem.as_bytes())?;
+ cert_file.sync_all()?;
+
+ // Prevent the certificate to be overwritten
+ set_permission(&cert_file, 0o444)?;
+
+ {
+ // Make sure the key is secret, before write
+ set_permission(&key_file, 0o600)?;
+
+ // Zero the private key on drop
+ let cert_key = cert.private_key_pem_string()?;
+ key_file.write_all(cert_key.as_bytes())?;
+ key_file.sync_all()?;
+
+ // Prevent the key to be overwritten
+ set_permission(&key_file, 0o400)?;
+ }
+
+ Ok(())
+ }
+}
+
+fn create_new_file(path: impl AsRef<Path>) -> Result<File, CertError> {
+ Ok(OpenOptions::new().write(true).create_new(true).open(path)?)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use assert_matches::assert_matches;
+ use std::fs;
+ use tedge_users::UserManager;
+ use tempfile::*;
+
+ #[test]
+ fn basic_usage() {
+ let dir = tempdir().unwrap();
+ let cert_path = temp_file_path(&dir, "my-device-cert.pem");
+ let key_path = temp_file_path(&dir, "my-device-key.pem");
+ let id = "my-device-id";
+
+ let cmd = CreateCertCmd {
+ id: String::from(id),
+ cert_path: cert_path.clone(),
+ key_path: key_path.clone(),
+ user_manager: UserManager::new(),
+ };
+
+ assert_matches!(
+ cmd.create_test_certificate(&NewCertificateConfig::default()),
+ Ok(())
+ );
+ assert_eq!(parse_pem_file(&cert_path).unwrap().tag, "CERTIFICATE");
+ assert_eq!(parse_pem_file(&key_path).unwrap().tag, "PRIVATE KEY");
+ }
+
+ #[test]
+ fn check_certificate_is_not_overwritten() {
+ let dir = tempdir().unwrap();
+
+ let cert_path = temp_file_path(&dir, "my-device-cert.pem");
+ let key_path = temp_file_path(&dir, "my-device-key.pem");
+
+ let cert_content = "some cert content";
+ let key_content = "some key content";
+
+ fs::write(&cert_path, cert_content).unwrap();
+ fs::write(&key_path, key_content).unwrap();
+
+ let cmd = CreateCertCmd {
+ id: "my-device-id".into(),
+ cert_path: cert_path.clone(),
+ key_path: key_path.clone(),
+ user_manager: UserManager::new(),
+ };
+
+ assert!(cmd
+ .create_test_certificate(&NewCertificateConfig::default())
+ .ok()
+ .is_none());
+
+ assert_eq!(fs::read(&cert_path).unwrap(), cert_content.as_bytes());
+ assert_eq!(fs::read(&key_path).unwrap(), key_content.as_bytes());
+ }
+
+ #[test]
+ fn create_certificate_in_non_existent_directory() {
+ let dir = tempdir().unwrap();
+ let key_path = temp_file_path(&dir, "my-device-key.pem");
+ let cert_path = FilePath::from("/non/existent/cert/path");
+
+ let cmd = CreateCertCmd {
+ id: "my-device-id".into(),
+ cert_path,
+ key_path,
+ user_manager: UserManager::new(),
+ };
+
+ let cert_error = cmd
+ .create_test_certificate(&NewCertificateConfig::default())
+ .unwrap_err();
+ assert_matches!(cert_error, CertError::CertPathError { .. });
+ }
+
+ #[test]
+ fn create_key_in_non_existent_directory() {
+ let dir = tempdir().unwrap();
+ let cert_path = temp_file_path(&dir, "my-device-cert.pem");
+ let key_path = FilePath::from("/non/existent/key/path");
+
+ let cmd = CreateCertCmd {
+ id: "my-device-id".into(),
+ cert_path,
+ key_path,
+ user_manager: UserManager::new(),
+ };
+
+ let cert_error = cmd
+ .create_test_certificate(&NewCertificateConfig::default())
+ .unwrap_err();
+ assert_matches!(cert_error, CertError::KeyPathError { .. });
+ }
+
+ fn temp_file_path(dir: &TempDir, filename: &str) -> FilePath {
+ dir.path().join(filename).into()
+ }
+
+ fn parse_pem_file(path: impl AsRef<Path>) -> Result<pem::Pem, String> {
+ let content = fs::read(path).map_err(|err| err.to_string())?;
+ pem::parse(content).map_err(|err| err.to_string())
+ }
+}
diff --git a/crates/core/tedge/src/cli/certificate/error.rs b/crates/core/tedge/src/cli/certificate/error.rs
new file mode 100644
index 00000000..41e03c9a
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/error.rs
@@ -0,0 +1,172 @@
+use reqwest::StatusCode;
+use std::error::Error;
+use tedge_config::FilePath;
+use tedge_users::UserSwitchError;
+use tedge_utils::paths::PathsError;
+
+#[derive(thiserror::Error, Debug)]
+pub enum CertError {
+ #[error(
+ r#"A certificate already exists and would be overwritten.
+ Existing file: "{path}"
+ Run `tedge cert remove` first to generate a new certificate.
+ "#
+ )]
+ CertificateAlreadyExists { path: FilePath },
+
+ #[error(
+ r#"No certificate has been attached to that device.
+ Missing file: {path:?}
+ Run `tedge cert create` to generate a new certificate.
+ "#
+ )]
+ CertificateNotFound { path: FilePath },
+
+ #[error(
+ r#"No private key has been attached to that device.
+ Missing file: {path:?}
+ Run `tedge cert create` to generate a new key and certificate.
+ "#
+ )]
+ KeyNotFound { path: FilePath },
+
+ #[error(
+ r#"A private key already exists and would be overwritten.
+ Existing file: {path:?}
+ Run `tedge cert remove` first to generate a new certificate and private key.
+ "#
+ )]
+ KeyAlreadyExists { path: FilePath },
+
+ #[error(transparent)]
+ ConfigError(#[from] crate::ConfigError),
+
+ #[error("I/O error")]
+ IoError(#[from] std::io::Error),
+
+ #[error("Invalid device.cert.path path: {0}")]
+ CertPathError(PathsError),
+
+ #[error("Invalid device.key.path path: {0}")]
+ KeyPathError(PathsError),
+
+ #[error(transparent)]
+ CertificateError(#[from] certificate::CertificateError),
+
+ #[error(
+ r#"Certificate read error at: {1:?}
+ Run `tedge cert create` if you want to create a new certificate."#
+ )]
+ CertificateReadFailed(#[source] std::io::Error, String),
+
+ #[error(transparent)]
+ PathsError(#[from] PathsError),
+
+ #[error(transparent)]
+ ReqwestError(#[from] reqwest::Error),
+
+ #[error("Request returned with code: {0}")]
+ StatusCode(StatusCode),
+
+ #[error(transparent)]
+ UrlParseError(#[from] url::ParseError),
+
+ #[error(transparent)]
+ UserSwitchError(#[from] UserSwitchError),
+
+ #[error("HTTP Connection Problem: {msg} \nHint: {hint}")]
+ WebpkiValidation { hint: String, msg: String },
+}
+
+impl CertError {
+ /// Improve the error message in case the error in a IO error on the certificate file.
+ pub fn cert_context(self, path: FilePath) -> CertError {
+ match self {
+ CertError::IoError(ref err) => match err.kind() {
+ std::io::ErrorKind::AlreadyExists => CertError::CertificateAlreadyExists { path },
+ std::io::ErrorKind::NotFound => CertError::CertificateNotFound { path },
+ _ => self,
+ },
+ _ => self,
+ }
+ }
+
+ /// Improve the error message in case the error in a IO error on the private key file.
+ pub fn key_context(self, path: FilePath) -> CertError {
+ match self {
+ CertError::IoError(ref err) => match err.kind() {
+ std::io::ErrorKind::AlreadyExists => CertError::KeyAlreadyExists { path },
+ std::io::ErrorKind::NotFound => CertError::KeyNotFound { path },
+ _ => self,
+ },
+ _ => self,
+ }
+ }
+}
+
+// Our source of error here is quite deep into the dependencies and we need to dig through that to get to our certificates validator errors which are Box<&dyn Error> through 3-4 levels
+// source: hyper::Error(
+// Connect,
+// Custom {
+// kind: Other,
+// error: Custom {
+// kind: InvalidData,
+// error: WebPKIError(
+// ..., // This is where we need to get
+// ),
+// },
+// },
+// )
+// This chain may break if underlying crates change.
+pub(crate) fn get_webpki_error_from_reqwest(err: reqwest::Error) -> CertError {
+ if let Some(rustls::TLSError::WebPKIError(cert_validation_error)) = err
+ // get `hyper::Error::Connect`
+ .source()
+ .and_then(|hyper_error| hyper_error.downcast_ref::<hyper::Error>())
+ .and_then(|hyper_error| hyper_error.source())
+ // Surprise: `Custom` type is `std::io::Error`; this is our first `Custom`.
+ .and_then(|connect_error| connect_error.downcast_ref::<std::io::Error>())
+ // A shortcut to get ref to our error 2 layers down.
+ .and_then(|custom_error| custom_error.get_ref())
+ // This is our second `Custom`.
+ .and_then(|custom_error2| custom_error2.downcast_ref::<std::io::Error>())
+ // Get final error type from `Custom`.
+ .and_then(|custom_error2| custom_error2.get_ref())
+ .and_then(|webpki_error| webpki_error.downcast_ref::<rustls::TLSError>())
+ {
+ match cert_validation_error {
+ webpki::Error::CAUsedAsEndEntity => CertError::WebpkiValidation {
+ hint: "A CA certificate is used as an end-entity server certificate. Make sure that the certificate used is an end-entity certificate signed by CA certificate.".into(),
+ msg: cert_validation_error.to_string(),
+ },
+
+ webpki::Error::CertExpired => CertError::WebpkiValidation {
+ hint: "The server certificate has expired, the time it is being validated for is later than the certificate's `notAfter` time."
+ .into(),
+ msg: cert_validation_error.to_string(),
+ },
+
+ webpki::Error::CertNotValidYet => CertError::WebpkiValidation {
+ hint: "The server certificate is not valid yet, the time it is being validated for is earlier than the certificate's `notBefore` time.".into(),
+ msg: cert_validation_error.to_string(),
+ },
+
+ webpki::Error::EndEntityUsedAsCA => CertError::WebpkiValidation {
+ hint: "An end-entity certificate is used as a server CA certificate. Make sure that the certificate used is signed by a correct CA certificate.".into(),
+ msg: cert_validation_error.to_string(),
+ },
+
+ webpki::Error::InvalidCertValidity => CertError::WebpkiValidation {
+ hint: "The server certificate validity period (`notBefore`, `notAfter`) is invalid, maybe the `notAfter` time is earlier than the `notBefore` time.".into(),
+ msg: cert_validation_error.to_string(),
+ },
+
+ _ => CertError::WebpkiValidation {
+ hint: "Server certificate validation error.".into(),
+ msg: cert_validation_error.to_string(),
+ },
+ }
+ } else {
+ CertError::ReqwestError(err) // any other Error type than `hyper::Error`
+ }
+}
diff --git a/crates/core/tedge/src/cli/certificate/mod.rs b/crates/core/tedge/src/cli/certificate/mod.rs
new file mode 100644
index 00000000..e37f2fb6
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/mod.rs
@@ -0,0 +1,8 @@
+pub use self::cli::TEdgeCertCli;
+
+mod cli;
+mod create;
+mod error;
+mod remove;
+mod show;
+mod upload;
diff --git a/crates/core/tedge/src/cli/certificate/remove.rs b/crates/core/tedge/src/cli/certificate/remove.rs
new file mode 100644
index 00000000..e46acfbf
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/remove.rs
@@ -0,0 +1,38 @@
+use super::error::CertError;
+use crate::command::Command;
+use tedge_config::*;
+use tedge_users::UserManager;
+use tedge_utils::paths::ok_if_not_found;
+
+/// Remove the device certificate
+pub struct RemoveCertCmd {
+ /// The path of the certificate to be removed
+ pub cert_path: FilePath,
+
+ /// The path of the private key to be removed
+ pub key_path: FilePath,
+
+ /// The UserManager required to change effective user id.
+ pub user_manager: UserManager,
+}
+
+impl Command for RemoveCertCmd {
+ fn description(&self) -> String {
+ "remove the device certificate".into()
+ }
+
+ fn execute(&self) -> anyhow::Result<()> {
+ let () = self.remove_certificate()?;
+ Ok(())
+ }
+}
+
+impl RemoveCertCmd {
+ fn remove_certificate(&self) -> Result<(), CertError> {
+ let _user_guard = self.user_manager.become_user(tedge_users::BROKER_USER)?;
+ std::fs::remove_file(&self.cert_path).or_else(ok_if_not_found)?;
+ std::fs::remove_file(&self.key_path).or_else(ok_if_not_found)?;
+
+ Ok(())
+ }
+}
diff --git a/crates/core/tedge/src/cli/certificate/show.rs b/crates/core/tedge/src/cli/certificate/show.rs
new file mode 100644
index 00000000..8890f308
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/show.rs
@@ -0,0 +1,41 @@
+use super::error::CertError;
+use crate::command::Command;
+
+use certificate::PemCertificate;
+use tedge_config::*;
+
+/// Show the device certificate, if any
+pub struct ShowCertCmd {
+ /// The path where the device certificate will be stored
+ pub cert_path: FilePath,
+}
+
+impl Command for ShowCertCmd {
+ fn description(&self) -> String {
+ "show the device certificate".into()
+ }
+
+ fn execute(&self) -> anyhow::Result<()> {
+ let () = self.show_certificate()?;
+ Ok(())
+ }
+}
+
+impl ShowCertCmd {
+ fn show_certificate(&self) -> Result<(), CertError> {
+ let pem = PemCertificate::from_pem_file(&self.cert_path).map_err(|err| match err {
+ certificate::CertificateError::IoError(from) => {
+ CertError::IoError(from).cert_context(self.cert_path.clone())
+ }
+ from => CertError::CertificateError(from),
+ })?;
+
+ println!("Device certificate: {}", self.cert_path);
+ println!("Subject: {}", pem.subject()?);
+ println!("Issuer: {}", pem.issuer()?);
+ println!("Valid from: {}", pem.not_before()?);
+ println!("Valid up to: {}", pem.not_after()?);
+ println!("Thumbprint: {}", pem.thumbprint()?);
+ Ok(())
+ }
+}
diff --git a/crates/core/tedge/src/cli/certificate/upload.rs b/crates/core/tedge/src/cli/certificate/upload.rs
new file mode 100644
index 00000000..ba5141d8
--- /dev/null
+++ b/crates/core/tedge/src/cli/certificate/upload.rs
@@ -0,0 +1,251 @@
+use super::error::{get_webpki_error_from_reqwest, CertError};
+use crate::command::Command;
+use reqwest::{StatusCode, Url};
+use std::{io::prelude::*, path::Path};
+use tedge_config::*;
+use tedge_utils::paths::pathbuf_to_string;
+
+#[derive(Debug, serde::Deserialize)]
+struct CumulocityResponse {
+ name: String,
+}
+
+#[derive(Debug, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+struct UploadCertBody {
+ name: String,
+ cert_in_pem_format: String,
+ auto_registration_enabled: bool,
+ status: String,
+}
+
+pub struct UploadCertCmd {
+ pub device_id: String,
+ pub path: FilePath,
+ pub host: ConnectUrl,
+ pub username: String,
+}
+
+impl Command for UploadCertCmd {
+ fn description(&self) -> String {
+ "upload root certificate".into()
+ }
+
+ fn execute(&self) -> anyhow::Result<()> {
+ Ok(self.upload_certificate()?)
+ }
+}
+
+impl UploadCertCmd {
+ fn upload_certificate(&self) -> Result<(), CertError> {
+ // 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: "))?,
+ };
+
+ // Use a builder instead of `Client::new`, `new` could panic, builder adds option to allow invalid certs.
+ let client = reqwest::blocking::Client::builder().build()?;
+
+ // To post certificate c8y requires one of the following endpoints:
+ // https://<tenant_id>.cumulocity.url.io/tenant/tenants/<tenant_id>/trusted-certificates
+ // https://<tenant_domain>.cumulocity.url.io/tenant/tenants/<tenant_id>/trusted-certificates
+ // and therefore we need to get tenant_id.
+ let tenant_id = get_tenant_id_blocking(
+ &client,
+ build_get_tenant_id_url(self.host.as_str())?,
+ &self.username,
+ &password,
+ )?;
+ self.post_certificate(&client, &tenant_id, &password)
+ }
+
+ fn post_certificate(
+ &self,
+ client: &reqwest::blocking::Client,
+ tenant_id: &str,
+ password: &str,
+ ) -> Result<(), CertError> {
+ let post_url = build_upload_certificate_url(self.host.as_str(), tenant_id)?;
+
+ let post_body = UploadCertBody {
+ auto_registration_enabled: true,
+ cert_in_pem_format: read_cert_to_string(&self.path)?,
+ name: self.device_id.clone(),
+ status: "ENABLED".into(),
+ };