Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions crates/core/c8y_api/src/http_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,19 @@ impl C8yEndPoint {
c8y_profile: Option<&str>,
) -> Result<Self, C8yEndPointConfigError> {
let c8y_config = tedge_config.c8y.try_get(c8y_profile)?;
let c8y_host = c8y_config.proxy.client.host.to_string();
let c8y_mqtt_host = c8y_host.clone();
let auth_proxy_addr = c8y_config.proxy.client.host.clone();
let auth_proxy_port = c8y_config.proxy.client.port;
let auth_proxy_protocol = c8y_config
.proxy
.cert_path
.or_none()
.map_or(Protocol::Http, |_| Protocol::Https);
let c8y_host = format!(
"{}://{auth_proxy_addr}:{auth_proxy_port}",
auth_proxy_protocol.as_str()
);
let c8y_mqtt_host = c8y_host.clone();
let proxy = ProxyUrlGenerator::new(auth_proxy_addr, auth_proxy_port, auth_proxy_protocol);

Ok(C8yEndPoint {
c8y_host,
c8y_mqtt_host,
Expand Down
45 changes: 25 additions & 20 deletions crates/core/tedge/src/cli/certificate/c8y/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Command for DownloadCertCmd {

impl DownloadCertCmd {
async fn download_device_certificate(&self) -> Result<(), Error> {
let (common_name, security_token) = self.get_registration_data()?;
let (common_name, security_token) = self.get_registration_data().await?;
if self.generate_csr {
create_device_csr(
common_name.clone(),
Expand Down Expand Up @@ -129,25 +129,30 @@ impl DownloadCertCmd {
/// Prompt the user for the device id and the security token
///
/// - unless already set on the command line or using env variables.
fn get_registration_data(&self) -> Result<(String, String), std::io::Error> {
let device_id = if self.device_id.is_empty() {
print!("Enter device id: ");
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
input.trim_end_matches(['\n', '\r']).to_string()
} else {
self.device_id.clone()
};

// Read the security token from /dev/tty
let security_token = if self.security_token.is_empty() {
rpassword::read_password_from_tty(Some("Enter security token: "))?
} else {
self.security_token.clone()
};

Ok((device_id, security_token))
async fn get_registration_data(&self) -> Result<(String, String), std::io::Error> {
let self_device_id = self.device_id.clone();
let self_security_token = self.security_token.clone();
tokio::task::spawn_blocking(move || {
let device_id = if self_device_id.is_empty() {
print!("Enter device id: ");
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
input.trim_end_matches(['\n', '\r']).to_string()
} else {
self_device_id
};

// Read the security token from /dev/tty
let security_token = if self_security_token.is_empty() {
rpassword::read_password_from_tty(Some("Enter security token: "))?
} else {
self_security_token
};

Ok((device_id, security_token))
})
.await?
}

/// Post the device CSR
Expand Down
41 changes: 19 additions & 22 deletions crates/core/tedge/src/cli/certificate/c8y/renew.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::cli::certificate::c8y::create_device_csr;
use crate::cli::certificate::c8y::read_csr_from_file;
use crate::cli::certificate::c8y::store_device_cert;
use crate::command::Command;
use crate::error;
use crate::get_webpki_error_from_reqwest;
use crate::log::MaybeFancy;
use anyhow::anyhow;
use anyhow::Error;
use c8y_api::http_proxy::C8yEndPoint;
use camino::Utf8PathBuf;
Expand Down Expand Up @@ -47,7 +47,10 @@ pub struct RenewCertCmd {
#[async_trait::async_trait]
impl Command for RenewCertCmd {
fn description(&self) -> String {
format!("Renew the device certificate from {}", self.c8y_url())
format!(
"renew the device certificate via Cumulocity HTTP proxy {}",
self.c8y_url()
)
}

async fn execute(&self) -> Result<(), MaybeFancy<Error>> {
Expand Down Expand Up @@ -89,29 +92,23 @@ impl RenewCertCmd {
let url = Url::parse(&url)?;
let result = self.post_device_csr(&http, &url, &csr).await;
match result {
Ok(response) if response.status() == StatusCode::OK => {
if let Ok(cert) = response.text().await {
Ok(response) if response.status() == StatusCode::OK => match response.text().await {
Ok(cert) => {
store_device_cert(&self.cert_path, cert).await?;
return Ok(());
Ok(())
}
error!("Fail to extract a certificate from the response returned by {url}");
}
Ok(response) => {
error!(
"The device certificate cannot be renewed from {url}:\n\t{} {}",
response.status(),
response.text().await.unwrap_or("".to_string())
);
}
Err(err) => {
error!(
"Fail to connect to {url}: {:?}",
get_webpki_error_from_reqwest(err)
)
}
Err(err) => Err(anyhow!(
"Fail to extract a certificate from the response: {err}"
)),
},
Ok(response) => Err(anyhow!(
"The request failed with {}:\n\t{}",
response.status(),
response.text().await.unwrap_or("".to_string())
)),
Err(err) => Err(Error::new(get_webpki_error_from_reqwest(err))
.context(format!("Fail to connect to Cumulocity HTTP proxy {url}"))),
}

Ok(())
}

/// Post the device CSR
Expand Down
7 changes: 7 additions & 0 deletions crates/core/tedge/src/cli/certificate/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ pub async fn reuse_private_key(key_path: &Utf8PathBuf) -> Result<KeyKind, std::i
tokio::fs::read_to_string(key_path)
.await
.map(|keypair_pem| KeyKind::Reuse { keypair_pem })
.or_else(|err| {
if key_path.exists() {
Err(err)
} else {
Ok(KeyKind::New)
}
})
Comment on lines +169 to +175
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root cause of the error-prone hint was to conflate all error cases into a file not found error.

}

async fn persist_private_key(
Expand Down
5 changes: 4 additions & 1 deletion crates/core/tedge/src/cli/certificate/create_csr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ impl CreateCsrCmd {
let csr_path = &self.csr_path;
let key_path = &self.key_path;

let previous_key = reuse_private_key(key_path).await.unwrap_or(KeyKind::New);
let previous_key = reuse_private_key(key_path)
.await
.map_err(|e| CertError::IoError(e).key_context(key_path.clone()))?;

let cert =
KeyCertPair::new_certificate_sign_request(&self.csr_template, id, &previous_key)?;

Expand Down
46 changes: 35 additions & 11 deletions crates/core/tedge/src/cli/certificate/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ pub enum CertError {
)]
CertificateNotFound { path: Utf8PathBuf },

#[error("I/O error accessing the certificate: {path:?}")]
CertificateIoError {
path: Utf8PathBuf,
#[source]
source: std::io::Error,
},

#[error(
r#"No private key has been attached to that device.
Missing file: {path:?}
Expand All @@ -41,6 +48,13 @@ pub enum CertError {
)]
KeyAlreadyExists { path: Utf8PathBuf },

#[error("I/O error accessing the private key: {path:?}")]
KeyIoError {
path: Utf8PathBuf,
#[source]
source: std::io::Error,
},

#[error(transparent)]
ConfigError(#[from] crate::ConfigError),

Expand All @@ -62,15 +76,15 @@ pub enum CertError {
#[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("Connection error: {0}")]
ReqwestConnect(String),

#[error("Request time out")]
ReqwestTimeout,

#[error(transparent)]
ReqwestError(#[from] reqwest::Error),

Expand Down Expand Up @@ -100,10 +114,10 @@ impl CertError {
/// Improve the error message in case the error in a IO error on the certificate file.
pub fn cert_context(self, path: Utf8PathBuf) -> CertError {
match self {
CertError::IoError(ref err) => match err.kind() {
CertError::IoError(err) => match err.kind() {
std::io::ErrorKind::AlreadyExists => CertError::CertificateAlreadyExists { path },
std::io::ErrorKind::NotFound => CertError::CertificateNotFound { path },
_ => self,
_ => CertError::CertificateIoError { path, source: err },
},
_ => self,
}
Expand All @@ -112,10 +126,10 @@ impl CertError {
/// Improve the error message in case the error in a IO error on the private key file.
pub fn key_context(self, path: Utf8PathBuf) -> CertError {
match self {
CertError::IoError(ref err) => match err.kind() {
CertError::IoError(err) => match err.kind() {
std::io::ErrorKind::AlreadyExists => CertError::KeyAlreadyExists { path },
std::io::ErrorKind::NotFound => CertError::KeyNotFound { path },
_ => self,
_ => CertError::KeyIoError { path, source: err },
},
_ => self,
}
Expand Down Expand Up @@ -153,6 +167,16 @@ pub fn get_webpki_error_from_reqwest(err: reqwest::Error) -> CertError {
{
CertError::CertificateError(tls_error)
} else {
CertError::ReqwestError(err) // any other Error type than `hyper::Error`
// any other Error type than `hyper::Error`
if err.is_connect() {
match err.source().and_then(|err| err.source()) {
Some(io_error) => CertError::ReqwestConnect(format!("{io_error}")),
None => CertError::ReqwestError(err),
}
} else if err.is_timeout() {
CertError::ReqwestTimeout
} else {
CertError::ReqwestError(err)
}
}
}
10 changes: 6 additions & 4 deletions crates/core/tedge/src/cli/certificate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub use self::cli::TEdgeCertCli;
use std::path::Path;
use camino::Utf8Path;
use tokio::io::AsyncReadExt;

mod c8y;
Expand All @@ -15,10 +15,12 @@ pub use self::cli::*;
pub use self::create::*;
pub use self::error::*;

pub(crate) async fn read_cert_to_string(path: impl AsRef<Path>) -> Result<String, CertError> {
pub(crate) async fn read_cert_to_string(path: impl AsRef<Utf8Path>) -> Result<String, CertError> {
let mut file = tokio::fs::File::open(path.as_ref()).await.map_err(|err| {
let path = path.as_ref().display().to_string();
CertError::CertificateReadFailed(err, path)
CertError::CertificateIoError {
source: err,
path: path.as_ref().to_owned(),
}
})?;
let mut content = String::new();
file.read_to_string(&mut content).await?;
Expand Down
5 changes: 0 additions & 5 deletions crates/core/tedge/src/cli/certificate/renew.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ impl RenewCertCmd {
let key_path = &self.key_path;
let id = certificate_cn(cert_path).await?;

// Remove only certificate
tokio::fs::remove_file(&self.cert_path)
.await
.map_err(|e| CertError::IoError(e).cert_context(self.cert_path.clone()))?;

// Re-create the certificate from the key, with new validity
let previous_key = reuse_private_key(key_path)
.await
Expand Down