diff --git a/Cargo.lock b/Cargo.lock index c703f11..375dbef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "base64-serde" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64 0.21.0", + "serde", +] + [[package]] name = "base64ct" version = "1.5.3" @@ -2421,6 +2431,7 @@ name = "tfclient" version = "0.1.0" dependencies = [ "base64 0.21.0", + "base64-serde", "chrono", "clap", "ctrlc", diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index 9c21ab1..df081aa 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -23,7 +23,7 @@ reqwest = { version = "0.11.16", features = ["blocking"] } base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" - +base64-serde = "0.7.0" [build-dependencies] serde = { version = "1.0.157", features = ["derive"] } diff --git a/tfclient/src/api.rs b/tfclient/src/api.rs index 5d5653a..fd3e77e 100644 --- a/tfclient/src/api.rs +++ b/tfclient/src/api.rs @@ -1,61 +1,13 @@ use std::error::Error; +use base64_serde::base64_serde_type; use log::trace; use reqwest::blocking::Client; use serde::{Serialize, Deserialize}; use url::Url; -#[derive(Serialize, Deserialize)] -pub struct EnrollRequest { - pub code: String, - #[serde(rename = "dhPubkey")] - pub dh_pubkey: String, - #[serde(rename = "edPubkey")] - pub ed_pubkey: String, - pub timestamp: String, -} +const ENDPOINT_V1: &str = "/v1/dnclient"; -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseMetadata {} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseOrganization { - pub id: String, - pub name: String, -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponseData { - pub config: String, - #[serde(rename = "hostID")] - pub host_id: String, - pub counter: i64, - #[serde(rename = "trustedKeys")] - pub trusted_keys: String, - pub organization: EnrollResponseOrganization, -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollResponse { - pub data: EnrollResponseData, - pub metadata: EnrollResponseMetadata, -} - -#[derive(Serialize, Deserialize)] -#[serde(untagged)] -pub enum APIResponse { - Error(EnrollError), - Success(EnrollResponse) -} - -#[derive(Serialize, Deserialize)] -pub struct EnrollError { - pub errors: Vec -} -#[derive(Serialize, Deserialize)] -pub struct EnrollErrorSingular { - pub code: String, - pub message: String -} +base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); pub fn enroll(server: &Url, request: &EnrollRequest) -> Result> { let endpoint = server.join("/v2/enroll")?; @@ -67,4 +19,127 @@ pub fn enroll(server: &Url, request: &EnrollRequest) -> Result +} + +#[derive(Serialize, Deserialize)] +pub struct RequestWrapper { + #[serde(rename = "type")] + pub message_type: String, + #[serde(with = "Base64Standard")] + pub value: Vec, + pub timestamp: String +} + +#[derive(Serialize, Deserialize)] +pub struct SignedResponseWrapper { + pub data: SignedResponse +} + +#[derive(Serialize, Deserialize)] +pub struct SignedResponse { + pub version: i32, + #[serde(with = "Base64Standard")] + pub message: Vec, + #[serde(with = "Base64Standard")] + pub signature: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct CheckForUpdateResponseWrapper { + pub data: CheckForUpdateResponse +} + +#[derive(Serialize, Deserialize)] +pub struct CheckForUpdateResponse { + #[serde(rename = "updateAvailable")] + pub update_available: bool +} + +#[derive(Serialize, Deserialize)] +pub struct DoUpdateRequest { + #[serde(rename = "edPubkeyPEM")] + #[serde(with = "Base64Standard")] + pub ed_pubkey_pem: Vec, + #[serde(rename = "dhPubkeyPEM")] + #[serde(with = "Base64Standard")] + pub dh_pubkey_pem: Vec, + #[serde(with = "Base64Standard")] + pub nonce: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct DoUpdateResponse { + #[serde(with = "Base64Standard")] + pub config: Vec, + pub counter: u32, + #[serde(with = "Base64Standard")] + pub nonce: Vec, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + pub trusted_keys: Vec +} + +const ENROLL_ENDPOINT: &str = "/v2/enroll"; + +#[derive(Serialize, Deserialize)] +pub struct EnrollRequest { + pub code: String, + #[serde(rename = "dhPubkey")] + #[serde(with = "Base64Standard")] + pub dh_pubkey: Vec, + #[serde(rename = "edPubkey")] + #[serde(with = "Base64Standard")] + pub ed_pubkey: Vec, + pub timestamp: String +} + + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +pub enum EnrollResponse { + Success { + data: EnrollResponseData + }, + Error { + errors: APIErrors + } +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseData { + #[serde(with = "Base64Standard")] + pub config: Vec, + #[serde(rename = "hostID")] + pub host_id: String, + pub counter: u32, + #[serde(rename = "trustedKeys")] + #[serde(with = "Base64Standard")] + pub trusted_keys: Vec, + pub organization: EnrollResponseDataOrg +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollResponseDataOrg { + pub id: String, + pub name: String +} + +#[derive(Serialize, Deserialize)] +pub struct APIError { + pub code: String, + pub message: String, + pub path: Option +} + +pub type APIErrors = Vec; \ No newline at end of file diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 3eacefe..2d1c2fc 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -3,7 +3,6 @@ use base64::Engine; use chrono::Local; use log::{error, info, warn}; use url::Url; -use trifid_pki::ca::NebulaCAPool; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::rand_core::OsRng; @@ -14,7 +13,8 @@ use crate::daemon::ThreadMessageSender; pub enum APIWorkerMessage { Shutdown, - Enroll { code: String } + Enroll { code: String }, + Timer } pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver) { @@ -68,6 +68,22 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr info!("recv on command socket: shutdown, stopping"); break; }, + APIWorkerMessage::Timer => { + info!("updating config"); + let mut cdata = match load_cdata(&instance) { + Ok(c) => c, + Err(e) => { + error!("error in api worker thread: {}", e); + error!("APIWorker exiting with error"); + return; + } + }; + if cdata.host_id.is_none() { + info!("not enrolled, cannot perform config update"); + continue; + } + + }, APIWorkerMessage::Enroll { code } => { info!("recv on command socket: enroll {}", code); let mut cdata = match load_cdata(&instance) { diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index 052efc8..64ff697 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -8,6 +8,7 @@ use crate::config::load_config; use crate::main; use crate::nebulaworker::{nebulaworker_main, NebulaWorkerMessage}; use crate::socketworker::{socketworker_main, SocketWorkerMessage}; +use crate::timerworker::{timer_main, TimerWorkerMessage}; use crate::util::check_server_url; pub fn daemon_main(name: String, server: String) { @@ -28,11 +29,13 @@ pub fn daemon_main(name: String, server: String) { let (tx_api, rx_api) = mpsc::channel::(); let (tx_socket, rx_socket) = mpsc::channel::(); let (tx_nebula, rx_nebula) = mpsc::channel::(); + let (tx_timer, rx_timer) = mpsc::channel::(); let transmitter = ThreadMessageSender { socket_thread: tx_socket, api_thread: tx_api, - nebula_thread: tx_nebula + nebula_thread: tx_nebula, + timer_thread: tx_timer, }; let mainthread_transmitter = transmitter.clone(); @@ -59,6 +62,12 @@ pub fn daemon_main(name: String, server: String) { error!("Error sending shutdown message to socket worker thread: {}", e); } } + match mainthread_transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to timer worker thread: {}", e); + } + } }) { Ok(_) => (), Err(e) => { @@ -69,8 +78,6 @@ pub fn daemon_main(name: String, server: String) { info!("Starting API thread..."); - - let config_api = config.clone(); let transmitter_api = transmitter.clone(); let name_api = name.clone(); @@ -86,6 +93,12 @@ pub fn daemon_main(name: String, server: String) { nebulaworker_main(config_nebula, transmitter_nebula, rx_nebula); }); + info!("Starting timer thread..."); + let timer_transmitter = transmitter.clone(); + let timer_thread = thread::spawn(move || { + timer_main(timer_transmitter, rx_timer); + }); + info!("Starting socket worker thread..."); let socket_thread = thread::spawn(move || { socketworker_main(config, name.clone(), transmitter, rx_socket); @@ -111,6 +124,16 @@ pub fn daemon_main(name: String, server: String) { } info!("API thread exited"); + info!("Waiting for timer thread to exit..."); + match timer_thread.join() { + Ok(_) => (), + Err(_) => { + error!("Error waiting for timer thread to exit."); + std::process::exit(1); + } + } + info!("Timer thread exited"); + info!("Waiting for Nebula thread to exit..."); match nebula_thread.join() { Ok(_) => (), @@ -128,5 +151,6 @@ pub fn daemon_main(name: String, server: String) { pub struct ThreadMessageSender { pub socket_thread: Sender, pub api_thread: Sender, - pub nebula_thread: Sender + pub nebula_thread: Sender, + pub timer_thread: Sender } \ No newline at end of file diff --git a/tfclient/src/dnapi/mod.rs b/tfclient/src/dnapi/mod.rs new file mode 100644 index 0000000..e5697e1 --- /dev/null +++ b/tfclient/src/dnapi/mod.rs @@ -0,0 +1 @@ +/// Essentially a direct port of https://github.com/DefinedNet/dnapi \ No newline at end of file diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index 5331186..e8e11fa 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -25,6 +25,7 @@ pub mod apiworker; pub mod socketworker; pub mod api; pub mod socketclient; +pub mod timerworker; pub mod nebula_bin { include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); diff --git a/tfclient/src/socketworker.rs b/tfclient/src/socketworker.rs index 389edb8..6085826 100644 --- a/tfclient/src/socketworker.rs +++ b/tfclient/src/socketworker.rs @@ -12,6 +12,7 @@ use crate::apiworker::APIWorkerMessage; use crate::config::{load_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; use crate::nebulaworker::NebulaWorkerMessage; +use crate::timerworker::TimerWorkerMessage; pub enum SocketWorkerMessage { Shutdown @@ -214,6 +215,12 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm error!("Error sending shutdown message to socket worker thread: {}", e); } } + match transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) { + Ok(_) => (), + Err(e) => { + error!("Error sending shutdown message to timer worker thread: {}", e); + } + } }, JsonMessage::GetHostID {} => { diff --git a/tfclient/src/timerworker.rs b/tfclient/src/timerworker.rs new file mode 100644 index 0000000..7fabba1 --- /dev/null +++ b/tfclient/src/timerworker.rs @@ -0,0 +1,47 @@ +use std::ops::Add; +use std::sync::mpsc::{Receiver, TryRecvError}; +use std::time::{Duration, SystemTime}; +use log::{error, info}; +use crate::apiworker::APIWorkerMessage; +use crate::daemon::ThreadMessageSender; + +pub enum TimerWorkerMessage { + Shutdown +} + +pub fn timer_main(tx: ThreadMessageSender, rx: Receiver) { + let mut api_reload_timer = SystemTime::now().add(Duration::from_secs(60)); + + loop { + match rx.try_recv() { + Ok(msg) => { + match msg { + TimerWorkerMessage::Shutdown => { + info!("recv on command socket: shutdown, stopping"); + break; + } + } + }, + Err(e) => { + match e { + TryRecvError::Empty => {} + TryRecvError::Disconnected => { + error!("timerworker command socket disconnected, shutting down to prevent orphaning"); + break; + } + } + } + } + + if SystemTime::now().gt(&api_reload_timer) { + info!("Timer triggered: API_RELOAD_TIMER"); + api_reload_timer = SystemTime::now().add(Duration::from_secs(60)); + match tx.api_thread.send(APIWorkerMessage::Timer) { + Ok(_) => (), + Err(e) => { + error!("Error sending timer message to api worker thread: {}", e); + } + } + } + } +} \ No newline at end of file