dnapi-rs - make EnrollMeta cloneable

tfclient - finish enrollment routine
This commit is contained in:
core 2023-03-29 18:44:46 -04:00
parent a77e126319
commit bd76d760f4
Signed by: core
GPG Key ID: FDBF740DADDCEECF
11 changed files with 42 additions and 71 deletions

2
Cargo.lock generated
View File

@ -622,7 +622,7 @@ dependencies = [
[[package]] [[package]]
name = "dnapi-rs" name = "dnapi-rs"
version = "0.1.4" version = "0.1.5"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"base64-serde", "base64-serde",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "dnapi-rs" name = "dnapi-rs"
version = "0.1.4" version = "0.1.5"
edition = "2021" edition = "2021"
description = "A rust client for the Defined Networking API" description = "A rust client for the Defined Networking API"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
@ -22,8 +22,3 @@ serde_json = "1.0.95"
trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] } trifid-pki = { version = "0.1.6", path = "../trifid-pki", features = ["serde_derive"] }
rand = "0.8.5" rand = "0.8.5"
chrono = "0.4.24" chrono = "0.4.24"
[features]
default = ["blocking"]
blocking = ["reqwest/blocking"]
async = []

View File

@ -24,7 +24,7 @@ pub struct Client {
server_url: Url server_url: Url
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
/// A struct containing organization metadata returned as a result of enrollment /// A struct containing organization metadata returned as a result of enrollment
pub struct EnrollMeta { pub struct EnrollMeta {
/// The server organization ID this node is now a member of /// The server organization ID this node is now a member of

View File

@ -24,7 +24,7 @@ pub struct Client {
server_url: Url server_url: Url
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
/// A struct containing organization metadata returned as a result of enrollment /// A struct containing organization metadata returned as a result of enrollment
pub struct EnrollMeta { pub struct EnrollMeta {
/// The server organization ID this node is now a member of /// The server organization ID this node is now a member of

View File

@ -15,18 +15,10 @@
#![allow(clippy::too_many_lines)] #![allow(clippy::too_many_lines)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#[cfg(all(feature = "blocking", feature = "async"))]
compile_error!("Cannot compile with both blocking and async features at the same time. Please pick one or the other.");
pub mod message; pub mod message;
#[cfg(feature = "blocking")] pub mod client_blocking;
#[path = "client_blocking.rs"] pub mod client_async;
pub mod client;
#[cfg(feature = "async")]
#[path = "client_async.rs"]
pub mod client;
pub mod credentials; pub mod credentials;
pub mod crypto; pub mod crypto;

View File

@ -24,7 +24,7 @@ base64 = "0.21.0"
chrono = "0.4.24" chrono = "0.4.24"
ipnet = "2.7.1" ipnet = "2.7.1"
base64-serde = "0.7.0" base64-serde = "0.7.0"
dnapi-rs = { version = "0.1.2", path = "../dnapi-rs" } dnapi-rs = { version = "0.1.5", path = "../dnapi-rs" }
[build-dependencies] [build-dependencies]
serde = { version = "1.0.157", features = ["derive"] } serde = { version = "1.0.157", features = ["derive"] }

View File

@ -1,14 +1,17 @@
use std::fs;
use std::sync::mpsc::{Receiver, TryRecvError}; use std::sync::mpsc::{Receiver, TryRecvError};
use base64::Engine; use base64::Engine;
use chrono::Local; use chrono::Local;
use log::{error, info, warn}; use log::{error, info, warn};
use url::Url; use url::Url;
use dnapi_rs::client_blocking::Client;
use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public}; use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public};
use trifid_pki::ed25519_dalek::{SecretKey, SigningKey}; use trifid_pki::ed25519_dalek::{SecretKey, SigningKey};
use trifid_pki::rand_core::OsRng; use trifid_pki::rand_core::OsRng;
use trifid_pki::x25519_dalek::StaticSecret; use trifid_pki::x25519_dalek::StaticSecret;
use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::config::{load_cdata, save_cdata, TFClientConfig};
use crate::daemon::ThreadMessageSender; use crate::daemon::ThreadMessageSender;
use crate::dirs::get_nebulaconfig_file;
pub enum APIWorkerMessage { pub enum APIWorkerMessage {
Shutdown, Shutdown,
@ -19,6 +22,8 @@ pub enum APIWorkerMessage {
pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) { pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _transmitters: ThreadMessageSender, rx: Receiver<APIWorkerMessage>) {
let server = Url::parse(&url).unwrap(); let server = Url::parse(&url).unwrap();
let client = Client::new(format!("tfclient/{}", env!("CARGO_PKG_VERSION")), server).unwrap();
loop { loop {
match rx.try_recv() { match rx.try_recv() {
Ok(msg) => { Ok(msg) => {
@ -37,7 +42,7 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr
return; return;
} }
}; };
if cdata.host_id.is_none() { if cdata.creds.is_none() {
info!("not enrolled, cannot perform config update"); info!("not enrolled, cannot perform config update");
continue; continue;
} }
@ -53,63 +58,30 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr
return; return;
} }
}; };
if cdata. { if cdata.creds.is_some() {
warn!("enrollment failed: already enrolled"); warn!("enrollment failed: already enrolled");
continue; continue;
} }
let dh_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_x25519_public(&dh_key.to_bytes()));
let ed_encoded = base64::engine::general_purpose::STANDARD.encode(serialize_ed25519_public(&ed_key.to_bytes()));
let req = EnrollRequest {
code,
dh_pubkey: dh_encoded,
ed_pubkey: ed_encoded,
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(),
};
let res = match enroll(&server, &req) {
Ok(res) => res,
Err(e) => {
error!("error in api worker thread: {}", e);
error!("APIWorker exiting with error");
return;
}
};
let resp = match res {
APIResponse::Error(e) => {
error!("error with enrollment: {}: {}", e.errors[0].code, e.errors[0].message);
continue;
}
APIResponse::Success(resp) => resp
};
info!("Enrolled with server. Host-ID {} config count {}", resp.data.host_id, resp.data.counter); let (config, dh_privkey, creds, meta) = match client.enroll(&code) {
info!("KeyPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id); Ok(resp) => resp,
info!("Config: {}", resp.data.config);
// Decode the CAPool and config
let key_pool_pem = match base64::engine::general_purpose::STANDARD.decode(resp.data.trusted_keys) {
Ok(p) => p,
Err(e) => { Err(e) => {
error!("error with enrollment: {}", e); error!("error with enrollment request: {}", e);
continue; continue;
} }
}; };
let config = match base64::engine::general_purpose::STANDARD.decode(resp.data.config) { match fs::write(get_nebulaconfig_file(&instance).expect("Unable to determine nebula config file location"), config) {
Ok(p) => p, Ok(_) => (),
Err(e) => { Err(e) => {
error!("error with enrollment: {}", e); error!("unable to save nebula config: {}", e);
continue; continue;
} }
}; }
let config_str = String::from_utf8(config).unwrap();
cdata.creds = Some(creds);
cdata.host_id = Some(resp.data.host_id); cdata.dh_privkey = Some(dh_privkey.try_into().expect("32 != 32"));
cdata.counter = resp.data.counter as i32; cdata.meta = Some(meta);
cdata.key_pool = Some(String::from_utf8(key_pool_pem).unwrap());
cdata.org_name = Some(resp.data.organization.name);
cdata.org_id = Some(resp.data.organization.id);
cdata.config = Some(config_str);
// Save vardata // Save vardata
match save_cdata(&instance, cdata) { match save_cdata(&instance, cdata) {
@ -120,6 +92,8 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, url: String, _tr
return; return;
} }
} }
info!("Configuration updated. Sending signal to Nebula worker thread");
} }
} }
}, },

View File

@ -5,6 +5,7 @@ use std::fs;
use log::{debug, info}; use log::{debug, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use dnapi_rs::client_blocking::EnrollMeta;
use dnapi_rs::credentials::Credentials; use dnapi_rs::credentials::Credentials;
use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file}; use crate::dirs::{get_cdata_dir, get_cdata_file, get_config_dir, get_config_file};
@ -21,7 +22,8 @@ pub struct TFClientConfig {
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct TFClientData { pub struct TFClientData {
pub dh_privkey: Option<[u8; 32]>, pub dh_privkey: Option<[u8; 32]>,
pub creds: Option<Credentials> pub creds: Option<Credentials>,
pub meta: Option<EnrollMeta>
} }
pub fn create_config(instance: &str) -> Result<(), Box<dyn Error>> { pub fn create_config(instance: &str) -> Result<(), Box<dyn Error>> {
@ -56,7 +58,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box<dyn Error>> {
info!("Creating data directory..."); info!("Creating data directory...");
fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?; fs::create_dir_all(get_cdata_dir(instance).ok_or("Unable to load data dir")?)?;
info!("Copying default data file to config directory..."); info!("Copying default data file to config directory...");
let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, key_pool: None, org_id: None, org_name: None, config: None }; let config = TFClientData { dh_privkey: None, creds: None, meta: None };
let config_str = toml::to_string(&config)?; let config_str = toml::to_string(&config)?;
fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?; fs::write(get_cdata_file(instance).ok_or("Unable to load data dir")?, config_str)?;
Ok(()) Ok(())

View File

@ -19,3 +19,7 @@ pub fn get_cdata_dir(instance: &str) -> Option<PathBuf> {
pub fn get_cdata_file(instance: &str) -> Option<PathBuf> { pub fn get_cdata_file(instance: &str) -> Option<PathBuf> {
get_cdata_dir(instance).map(|f| f.join("tfclient.toml")) get_cdata_dir(instance).map(|f| f.join("tfclient.toml"))
} }
pub fn get_nebulaconfig_file(instance: &str) -> Option<PathBuf> {
get_cdata_dir(instance).map(|f| f.join("nebula.sk_embedded.yml"))
}

View File

@ -6,7 +6,8 @@ use crate::config::TFClientConfig;
use crate::daemon::ThreadMessageSender; use crate::daemon::ThreadMessageSender;
pub enum NebulaWorkerMessage { pub enum NebulaWorkerMessage {
Shutdown Shutdown,
ConfigUpdated
} }
pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, rx: Receiver<NebulaWorkerMessage>) { pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSender, rx: Receiver<NebulaWorkerMessage>) {
@ -17,6 +18,9 @@ pub fn nebulaworker_main(_config: TFClientConfig, _transmitter: ThreadMessageSen
NebulaWorkerMessage::Shutdown => { NebulaWorkerMessage::Shutdown => {
info!("recv on command socket: shutdown, stopping"); info!("recv on command socket: shutdown, stopping");
break; break;
},
NebulaWorkerMessage::ConfigUpdated => {
info!("our configuration has been updated - reloading");
} }
} }
}, },

View File

@ -232,8 +232,8 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm
} }
}; };
client.stream.write_all(&ctob(JsonMessage::HostID { client.stream.write_all(&ctob(JsonMessage::HostID {
has_id: data.host_id.is_some(), has_id: data.creds.is_some(),
id: data.host_id id: data.creds.map(|c| c.host_id)
}))?; }))?;
}, },