diff --git a/Cargo.lock b/Cargo.lock index fc1592a..66579ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "dnapi-rs" -version = "0.1.6" +version = "0.1.7" dependencies = [ "base64 0.21.0", "base64-serde", @@ -2803,7 +2803,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.8" +version = "0.1.9" dependencies = [ "ed25519-dalek", "hex", diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml index 2991ff6..7a0bebc 100644 --- a/dnapi-rs/Cargo.toml +++ b/dnapi-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dnapi-rs" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "A rust client for the Defined Networking API" license = "AGPL-3.0-or-later" diff --git a/dnapi-rs/src/client_async.rs b/dnapi-rs/src/client_async.rs index 90d8266..3badec2 100644 --- a/dnapi-rs/src/client_async.rs +++ b/dnapi-rs/src/client_async.rs @@ -11,6 +11,7 @@ use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; use crate::crypto::{new_keys, nonce}; use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; use serde::{Serialize, Deserialize}; +use base64::Engine; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -185,12 +186,18 @@ impl Client { timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), })?; let encoded_msg_bytes = encoded_msg.into_bytes(); - let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let b64_msg = base64::engine::general_purpose::STANDARD.encode(encoded_msg_bytes); + let b64_msg_bytes = b64_msg.as_bytes(); + let signature = ed_privkey.sign(b64_msg_bytes).to_vec(); + + ed_privkey.verify(b64_msg_bytes, &Signature::from_slice(&signature)?)?; + debug!("signature valid via clientside check"); + let body = RequestV1 { version: 1, host_id: host_id.to_string(), counter, - message: encoded_msg_bytes, + message: b64_msg, signature, }; diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index 5b372e8..bd932e9 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -1,8 +1,9 @@ //! Client structs to handle communication with the Defined Networking API. This is the blocking client API - if you want async instead, set no-default-features and enable the async feature instead. use std::error::Error; +use base64::Engine; use chrono::Local; -use log::{debug, error}; +use log::{debug, error, trace}; use reqwest::StatusCode; use url::Url; use trifid_pki::cert::serialize_ed25519_public; @@ -90,6 +91,9 @@ impl Client { organization_name: r.organization.name, }; + debug!("parsing public keys"); + + let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?; let creds = Credentials { @@ -190,17 +194,25 @@ impl Client { timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), })?; let encoded_msg_bytes = encoded_msg.into_bytes(); - let signature = ed_privkey.sign(&encoded_msg_bytes).to_vec(); + let b64_msg = base64::engine::general_purpose::STANDARD.encode(encoded_msg_bytes); + let b64_msg_bytes = b64_msg.as_bytes(); + let signature = ed_privkey.sign(b64_msg_bytes).to_vec(); + + ed_privkey.verify(b64_msg_bytes, &Signature::from_slice(&signature)?)?; + debug!("signature valid via clientside check"); + let body = RequestV1 { version: 1, host_id: host_id.to_string(), counter, - message: encoded_msg_bytes, + message: b64_msg, signature, }; let post_body = serde_json::to_string(&body)?; + trace!("sending dnclient request {}", post_body); + let resp = self.http_client.post(self.server_url.join(ENDPOINT_V1)?).body(post_body).send()?; match resp.status() { diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index ce1e891..d1360ed 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -23,9 +23,8 @@ pub struct RequestV1 { pub host_id: String, /// The counter last returned by the server pub counter: u32, - #[serde(with = "Base64Standard")] - /// A base64-encoded message - pub message: Vec, + /// A base64-encoded message. This must be previously base64-encoded, as the signature is signed over the base64-encoded data. + pub message: String, #[serde(with = "Base64Standard")] /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key pub signature: Vec diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml index bd16e92..6d920e2 100644 --- a/tfclient/Cargo.toml +++ b/tfclient/Cargo.toml @@ -24,7 +24,7 @@ base64 = "0.21.0" chrono = "0.4.24" ipnet = "2.7.1" base64-serde = "0.7.0" -dnapi-rs = { version = "0.1.6", path = "../dnapi-rs" } +dnapi-rs = { version = "0.1.7", path = "../dnapi-rs" } serde_yaml = "0.9.19" [build-dependencies] diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index ed92faa..517a09b 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -145,7 +145,9 @@ pub struct NebulaConfig { pub struct NebulaConfigPki { pub ca: String, pub cert: String, - pub key: String, + #[serde(default = "none")] + #[serde(skip_serializing_if = "is_none")] + pub key: Option, #[serde(default = "empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")] pub blocklist: Vec, @@ -180,7 +182,9 @@ pub struct NebulaConfigLighthouse { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigLighthouseDns { - pub host: Ipv4Addr, + #[serde(default = "string_empty")] + #[serde(skip_serializing_if = "is_string_empty")] + pub host: String, #[serde(default = "u16_53")] #[serde(skip_serializing_if = "is_u16_53")] pub port: u16 @@ -188,9 +192,9 @@ pub struct NebulaConfigLighthouseDns { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NebulaConfigListen { - #[serde(default = "ipv4_0000")] - #[serde(skip_serializing_if = "is_ipv4_0000")] - pub host: Ipv4Addr, + #[serde(default = "string_empty")] + #[serde(skip_serializing_if = "is_string_empty")] + pub host: String, #[serde(default = "u16_0")] #[serde(skip_serializing_if = "is_u16_0")] pub port: u16, @@ -520,6 +524,9 @@ fn is_u64_1(u: &u64) -> bool { *u == 1 } fn string_nebula() -> String { "nebula".to_string() } fn is_string_nebula(s: &str) -> bool { s == "nebula" } +fn string_empty() -> String { String::new() } +fn is_string_empty(s: &str) -> bool { s == "" } + fn protocol_tcp() -> NebulaConfigStatsGraphiteProtocol { NebulaConfigStatsGraphiteProtocol::Tcp } fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) } diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index eadc28a..02c1230 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::fs; use std::sync::mpsc::{Receiver, TryRecvError}; +use std::time::{Duration, SystemTime}; use log::{debug, error, info}; use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig}; use crate::daemon::ThreadMessageSender; @@ -15,13 +16,16 @@ pub enum NebulaWorkerMessage { } fn insert_private_key(instance: &str) -> Result<(), Box> { + if !get_nebulaconfig_file(instance).ok_or("Could not get config file location")?.exists() { + return Ok(()); // cant insert private key into a file that does not exist - BUT. we can gracefully handle nebula crashing - we cannot gracefully handle this fn failing + } let cdata = load_cdata(instance)?; let key = cdata.dh_privkey.ok_or("Missing private key")?; let config_str = fs::read_to_string(get_nebulaconfig_file(instance).ok_or("Could not get config file location")?)?; let mut config: NebulaConfig = serde_yaml::from_str(&config_str)?; - config.pki.key = String::from_utf8(key)?; + config.pki.key = Some(String::from_utf8(key)?); debug!("inserted private key into config: {:?}", config); @@ -63,8 +67,25 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter }; info!("nebula process started"); + let mut last_restart_time = SystemTime::now(); + // dont need to save it, because we do not, in any circumstance, write to it loop { + if let Ok(e) = child.try_wait() { + if e.is_some() && SystemTime::now() > last_restart_time + Duration::from_secs(5) { + info!("nebula process has exited, restarting"); + child = match run_embedded_nebula(&["-config".to_string(), get_nebulaconfig_file(&instance).unwrap().to_str().unwrap().to_string()]) { + Ok(c) => c, + Err(e) => { + error!("unable to start embedded nebula binary: {}", e); + error!("nebula thread exiting with error"); + return; + } + }; + info!("nebula process started"); + last_restart_time = SystemTime::now(); + } + } match rx.try_recv() { Ok(msg) => { match msg { @@ -113,6 +134,7 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter return; } }; + last_restart_time = SystemTime::now(); debug!("nebula process restarted"); } } diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index 0cb0bee..fae5fb2 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.8" +version = "0.1.9" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "AGPL-3.0-or-later" diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index 190baf0..60b43fd 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -307,8 +307,8 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result, Box Result>, Box if pem.tag != ED25519_PUBLIC_KEY_BANNER { return Err(KeyError::WrongPemTag.into()) } - if pem.contents.len() != 64 { - return Err(KeyError::Not64Bytes.into()) + if pem.contents.len() != 32 { + return Err(KeyError::Not32Bytes.into()) } keys.push(pem.contents); } diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs index 3d328dc..2a409c5 100644 --- a/trifid-pki/src/test.rs +++ b/trifid-pki/src/test.rs @@ -296,19 +296,20 @@ fn x25519_serialization() { #[test] fn ed25519_serialization() { let bytes = [0u8; 64]; + let bytes2 = [0u8; 32]; assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes); assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); - assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); - assert!(deserialize_ed25519_public(&[0u8; 32]).is_err()); + assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes2)).unwrap(), bytes2); + assert!(deserialize_ed25519_public(&[0u8; 64]).is_err()); let mut bytes = vec![]; - bytes.append(&mut serialize_ed25519_public(&[0u8; 64])); - bytes.append(&mut serialize_ed25519_public(&[1u8; 64])); + bytes.append(&mut serialize_ed25519_public(&[0u8; 32])); + bytes.append(&mut serialize_ed25519_public(&[1u8; 32])); let deser = deserialize_ed25519_public_many(&bytes).unwrap(); - assert_eq!(deser[0], [0u8; 64]); - assert_eq!(deser[1], [1u8; 64]); + assert_eq!(deser[0], [0u8; 32]); + assert_eq!(deser[1], [1u8; 32]); - bytes.append(&mut serialize_ed25519_public(&[1u8; 63])); + bytes.append(&mut serialize_ed25519_public(&[1u8; 33])); deserialize_ed25519_public_many(&bytes).unwrap_err(); } @@ -647,11 +648,11 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB #[test] fn test_deserialize_ed25519_public() { - let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + let pub_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA ED25519 PUBLIC KEY-----"; let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= -----END NEBULA ED25519 PUBLIC KEY-----"; let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY----- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== @@ -660,7 +661,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== -END NEBULA ED25519 PUBLIC KEY-----"; - deserialize_ed25519_public(priv_key).unwrap(); + deserialize_ed25519_public(pub_key).unwrap(); deserialize_ed25519_public(short_key).unwrap_err();