summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge
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
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')
-rw-r--r--crates/core/tedge/Cargo.toml48
-rw-r--r--crates/core/tedge/README.md23
-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
-rw-r--r--crates/core/tedge/src/command.rs157
-rw-r--r--crates/core/tedge/src/error.rs23
-rw-r--r--crates/core/tedge/src/main.rs62
-rw-r--r--crates/core/tedge/src/system_services/command_builder.rs29
-rw-r--r--crates/core/tedge/src/system_services/error.rs63
-rw-r--r--crates/core/tedge/src/system_services/manager.rs40
-rw-r--r--crates/core/tedge/src/system_services/manager_ext.rs62
-rw-r--r--crates/core/tedge/src/system_services/managers/bsd.rs199
-rw-r--r--crates/core/tedge/src/system_services/managers/mod.rs6
-rw-r--r--crates/core/tedge/src/system_services/managers/null.rs35
-rw-r--r--crates/core/tedge/src/system_services/managers/openrc.rs199
-rw-r--r--crates/core/tedge/src/system_services/managers/systemd.rs217
-rw-r--r--crates/core/tedge/src/system_services/mod.rs20
-rw-r--r--crates/core/tedge/src/system_services/services.rs39
-rw-r--r--crates/core/tedge/tests/main.rs376
-rw-r--r--crates/core/tedge/tests/mqtt.rs64
-rw-r--r--crates/core/tedge/tests/os_related/mod.rs5
-rw-r--r--crates/core/tedge/tests/os_related/unix.rs122
53 files changed, 4981 insertions, 0 deletions
diff --git a/crates/core/tedge/Cargo.toml b/crates/core/tedge/Cargo.toml
new file mode 100644
index 00000000..1b3c1456
--- /dev/null
+++ b/crates/core/tedge/Cargo.toml
@@ -0,0 +1,48 @@
+[package]
+name = "tedge"
+version = "0.4.3"
+edition = "2018"
+authors = ["thin-edge.io team <info@thin-edge.io>"]
+license = "Apache-2.0"
+readme = "README.md"
+description = "tedge is the cli tool for thin-edge.io"
+
+[package.metadata.deb]
+depends = "mosquitto"
+maintainer-scripts = "configuration/debian/tedge"
+
+[dependencies]
+anyhow = "1.0"
+certificate = { path = "../../common/certificate" }
+chrono = "0.4"
+futures = "0.3"
+hyper = { version = "0.14", default-features = false }
+reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls", "stream"] }
+rpassword = "5.0"
+rumqttc = "0.10"
+rustls = "0.19"
+serde = { version = "1.0", features = ["derive"] }
+structopt = "0.3"
+tedge_config = { path = "../../common/tedge_config" }
+tedge_users = { path = "../../common/tedge_users" }
+tedge_utils = { path = "../../common/tedge_utils" }
+thiserror = "1.0"
+toml = "0.5"
+url = "2.2"
+webpki = "0.21"
+which = "4.2"
+
+[dev-dependencies]
+assert_cmd = "2.0"
+assert_matches = "1.5"
+mockito = "0.30"
+pem = "1.0"
+predicates = "2.0"
+tempfile = "3.2"
+
+[features]
+integration-test = []
+mosquitto-available = [] # Enable tests requesting mosquitto installed
+root-access = [] # Enable tests requesting root access
+tedge-user = [] # Enable tests requesting a tedge user
+openrc = [] # Enable usage of OpenRC
diff --git a/crates/core/tedge/README.md b/crates/core/tedge/README.md
new file mode 100644
index 00000000..af155398
--- /dev/null
+++ b/crates/core/tedge/README.md
@@ -0,0 +1,23 @@
+# thin-edge-cli
+
+tedge 0.1.0
+
+USAGE:
+
+ tedge [FLAGS] <SUBCOMMAND>
+
+FLAGS:
+
+ -h, --help Prints help information
+ -V, --version Prints version information
+ -v Verbose mode (-v, -vv, -vvv, etc.)
+
+SUBCOMMANDS:
+
+ config Configure Thin Edge
+ help Prints this message or the help of the given subcommand(s)
+
+Examples:
+
+ tedge --help
+ tedge config --help
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 sou