Skip to content

Commit

Permalink
Impl tedge cert download c8y
Browse files Browse the repository at this point in the history
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
  • Loading branch information
didier-wenzek committed Nov 22, 2024
1 parent 03a067e commit 284c8ee
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 20 deletions.
135 changes: 127 additions & 8 deletions crates/core/tedge/src/cli/certificate/download.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
use crate::bridge::BridgeLocation;
use crate::command::Command;
use crate::error;
use crate::get_webpki_error_from_reqwest;
use crate::log::MaybeFancy;
use crate::read_cert_to_string;
use crate::warning;
use crate::CertError;
use crate::CreateCertCmd;
use anyhow::Error;
use camino::Utf8PathBuf;
use certificate::CloudRootCerts;
use certificate::NewCertificateConfig;
use hyper::StatusCode;
use reqwest::blocking::Response;
use reqwest::header::CONTENT_TYPE;
use std::fs::OpenOptions;
use std::io::Write;
use std::time::Duration;
use tedge_config::HostPort;
use tedge_config::HTTPS_PORT;
use tedge_utils::paths::set_permission;
use url::Url;

pub struct C8yDownloadCertCmd {
/// The device identifier to be used as the common name for the certificate
Expand Down Expand Up @@ -38,14 +54,117 @@ impl Command for C8yDownloadCertCmd {
}

fn execute(&self) -> Result<(), MaybeFancy<Error>> {
eprintln!("{}", self.description());
eprintln!(" device_id = {}", self.device_id);
eprintln!(" security_token = {}", self.security_token);
eprintln!(" c8y_url = {}", self.c8y_url);
let _ = self.root_certs.clone();
eprintln!(" cert_path = {}", self.cert_path);
eprintln!(" key_path = {}", self.key_path);
eprintln!(" csr_path = {}", self.csr_path);
Ok(self.download_device_certificate()?)
}
}

impl C8yDownloadCertCmd {
fn download_device_certificate(&self) -> Result<(), Error> {
let (common_name, security_token) = self.get_registration_data()?;
let csr = self.create_device_csr(common_name.clone())?;

let http = self.root_certs.blocking_client();
let url = format!("https://{}/.well-known/est/simpleenroll", self.c8y_url);
let url = Url::parse(&url)?;

loop {
let result = self.post_device_csr(&http, &url, &common_name, &security_token, &csr);
match result {
Ok(response) if response.status() == StatusCode::OK => {
if let Ok(cert) = response.text() {
self.store_device_cert(cert)?;
return Ok(());
}
error!(
"Fail to extract a certificate from the response returned by {}",
self.c8y_url
);
}
Ok(response) => {
error!(
"The device {} is not registered yet on {}: {:?}",
common_name,
self.c8y_url,
response.status()
);
}
Err(err) => {
error!(
"Fail to connect to {}: {:?}",
self.c8y_url,
get_webpki_error_from_reqwest(err)
)
}
}
warning!("Will retry in 5 seconds");
std::thread::sleep(Duration::from_secs(5));
}
}

/// 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 password: "))?
} else {
self.security_token.clone()
};

Ok((device_id, security_token))
}

/// Create the device private key and CSR
fn create_device_csr(&self, common_name: String) -> Result<String, CertError> {
let config = NewCertificateConfig::default();
let create_cmd = CreateCertCmd {
id: common_name,
cert_path: self.cert_path.clone(),
key_path: self.key_path.clone(),
csr_path: Some(self.csr_path.clone()),
bridge_location: BridgeLocation::BuiltIn,
};
create_cmd.create_certificate_signing_request(&config)?;
read_cert_to_string(&self.csr_path)
}

/// Post the device CSR
fn post_device_csr(
&self,
http: &reqwest::blocking::Client,
url: &Url,
username: &str,
password: &str,
csr: &str,
) -> Result<Response, reqwest::Error> {
http.post(url.clone())
.basic_auth(username, Some(password))
.header(CONTENT_TYPE, "text/plain")
.body(csr.to_string())
.send()
}

fn store_device_cert(&self, cert: String) -> Result<(), CertError> {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&self.cert_path)?;

file.write_all(cert.as_bytes())?;
file.sync_all()?;

set_permission(&file, 0o444)?;
Ok(())
}
}
13 changes: 13 additions & 0 deletions crates/core/tedge/src/cli/certificate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub use self::cli::TEdgeCertCli;
use std::io::Read;
use std::path::Path;

mod cli;
mod create;
Expand All @@ -13,3 +15,14 @@ mod upload;
pub use self::cli::*;
pub use self::create::*;
pub use self::error::*;

pub(crate) fn read_cert_to_string(path: impl AsRef<Path>) -> Result<String, CertError> {
let mut file = std::fs::File::open(path.as_ref()).map_err(|err| {
let path = path.as_ref().display().to_string();
CertError::CertificateReadFailed(err, path)
})?;
let mut content = String::new();
file.read_to_string(&mut content)?;

Ok(content)
}
13 changes: 1 addition & 12 deletions crates/core/tedge/src/cli/certificate/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ use super::error::get_webpki_error_from_reqwest;
use super::error::CertError;
use crate::command::Command;
use crate::log::MaybeFancy;
use crate::read_cert_to_string;
use crate::warning;
use camino::Utf8PathBuf;
use certificate::CloudRootCerts;
use reqwest::StatusCode;
use reqwest::Url;
use std::io::prelude::*;
use std::path::Path;
use tedge_config::HostPort;
use tedge_config::HTTPS_PORT;

Expand Down Expand Up @@ -159,17 +159,6 @@ fn get_tenant_id_blocking(
Ok(body.name)
}

fn read_cert_to_string(path: impl AsRef<Path>) -> Result<String, CertError> {
let mut file = std::fs::File::open(path.as_ref()).map_err(|err| {
let path = path.as_ref().display().to_string();
CertError::CertificateReadFailed(err, path)
})?;
let mut content = String::new();
file.read_to_string(&mut content)?;

Ok(content)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit 284c8ee

Please sign in to comment.