tfclient is done! woo!

This commit is contained in:
c0repwn3r 2023-03-30 12:13:29 -04:00
parent 7513eb8318
commit fa40dcda5b
Signed by: core
GPG Key ID: FDBF740DADDCEECF
11 changed files with 82 additions and 34 deletions

4
Cargo.lock generated
View File

@ -622,7 +622,7 @@ dependencies = [
[[package]] [[package]]
name = "dnapi-rs" name = "dnapi-rs"
version = "0.1.6" version = "0.1.7"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"base64-serde", "base64-serde",
@ -2803,7 +2803,7 @@ dependencies = [
[[package]] [[package]]
name = "trifid-pki" name = "trifid-pki"
version = "0.1.8" version = "0.1.9"
dependencies = [ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"hex", "hex",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "dnapi-rs" name = "dnapi-rs"
version = "0.1.6" version = "0.1.7"
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"

View File

@ -11,6 +11,7 @@ use crate::credentials::{Credentials, ed25519_public_keys_from_pem};
use crate::crypto::{new_keys, nonce}; 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 crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use base64::Engine;
/// A type alias to abstract return types /// A type alias to abstract return types
pub type NebulaConfig = Vec<u8>; pub type NebulaConfig = Vec<u8>;
@ -185,12 +186,18 @@ impl Client {
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(),
})?; })?;
let encoded_msg_bytes = encoded_msg.into_bytes(); 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 { let body = RequestV1 {
version: 1, version: 1,
host_id: host_id.to_string(), host_id: host_id.to_string(),
counter, counter,
message: encoded_msg_bytes, message: b64_msg,
signature, signature,
}; };

View File

@ -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. //! 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 std::error::Error;
use base64::Engine;
use chrono::Local; use chrono::Local;
use log::{debug, error}; use log::{debug, error, trace};
use reqwest::StatusCode; use reqwest::StatusCode;
use url::Url; use url::Url;
use trifid_pki::cert::serialize_ed25519_public; use trifid_pki::cert::serialize_ed25519_public;
@ -90,6 +91,9 @@ impl Client {
organization_name: r.organization.name, organization_name: r.organization.name,
}; };
debug!("parsing public keys");
let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?; let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?;
let creds = Credentials { let creds = Credentials {
@ -190,17 +194,25 @@ impl Client {
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(), timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(),
})?; })?;
let encoded_msg_bytes = encoded_msg.into_bytes(); 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 { let body = RequestV1 {
version: 1, version: 1,
host_id: host_id.to_string(), host_id: host_id.to_string(),
counter, counter,
message: encoded_msg_bytes, message: b64_msg,
signature, signature,
}; };
let post_body = serde_json::to_string(&body)?; 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()?; let resp = self.http_client.post(self.server_url.join(ENDPOINT_V1)?).body(post_body).send()?;
match resp.status() { match resp.status() {

View File

@ -23,9 +23,8 @@ pub struct RequestV1 {
pub host_id: String, pub host_id: String,
/// The counter last returned by the server /// The counter last returned by the server
pub counter: u32, pub counter: u32,
#[serde(with = "Base64Standard")] /// A base64-encoded message. This must be previously base64-encoded, as the signature is signed over the base64-encoded data.
/// A base64-encoded message pub message: String,
pub message: Vec<u8>,
#[serde(with = "Base64Standard")] #[serde(with = "Base64Standard")]
/// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key
pub signature: Vec<u8> pub signature: Vec<u8>

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.6", path = "../dnapi-rs" } dnapi-rs = { version = "0.1.7", path = "../dnapi-rs" }
serde_yaml = "0.9.19" serde_yaml = "0.9.19"
[build-dependencies] [build-dependencies]

View File

@ -145,7 +145,9 @@ pub struct NebulaConfig {
pub struct NebulaConfigPki { pub struct NebulaConfigPki {
pub ca: String, pub ca: String,
pub cert: String, pub cert: String,
pub key: String, #[serde(default = "none")]
#[serde(skip_serializing_if = "is_none")]
pub key: Option<String>,
#[serde(default = "empty_vec")] #[serde(default = "empty_vec")]
#[serde(skip_serializing_if = "is_empty_vec")] #[serde(skip_serializing_if = "is_empty_vec")]
pub blocklist: Vec<String>, pub blocklist: Vec<String>,
@ -180,7 +182,9 @@ pub struct NebulaConfigLighthouse {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NebulaConfigLighthouseDns { 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(default = "u16_53")]
#[serde(skip_serializing_if = "is_u16_53")] #[serde(skip_serializing_if = "is_u16_53")]
pub port: u16 pub port: u16
@ -188,9 +192,9 @@ pub struct NebulaConfigLighthouseDns {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NebulaConfigListen { pub struct NebulaConfigListen {
#[serde(default = "ipv4_0000")] #[serde(default = "string_empty")]
#[serde(skip_serializing_if = "is_ipv4_0000")] #[serde(skip_serializing_if = "is_string_empty")]
pub host: Ipv4Addr, pub host: String,
#[serde(default = "u16_0")] #[serde(default = "u16_0")]
#[serde(skip_serializing_if = "is_u16_0")] #[serde(skip_serializing_if = "is_u16_0")]
pub port: u16, pub port: u16,
@ -520,6 +524,9 @@ fn is_u64_1(u: &u64) -> bool { *u == 1 }
fn string_nebula() -> String { "nebula".to_string() } fn string_nebula() -> String { "nebula".to_string() }
fn is_string_nebula(s: &str) -> bool { s == "nebula" } 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 protocol_tcp() -> NebulaConfigStatsGraphiteProtocol { NebulaConfigStatsGraphiteProtocol::Tcp }
fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) } fn is_protocol_tcp(p: &NebulaConfigStatsGraphiteProtocol) -> bool { matches!(p, NebulaConfigStatsGraphiteProtocol::Tcp) }

View File

@ -3,6 +3,7 @@
use std::error::Error; use std::error::Error;
use std::fs; use std::fs;
use std::sync::mpsc::{Receiver, TryRecvError}; use std::sync::mpsc::{Receiver, TryRecvError};
use std::time::{Duration, SystemTime};
use log::{debug, error, info}; use log::{debug, error, info};
use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig}; use crate::config::{load_cdata, NebulaConfig, save_cdata, TFClientConfig};
use crate::daemon::ThreadMessageSender; use crate::daemon::ThreadMessageSender;
@ -15,13 +16,16 @@ pub enum NebulaWorkerMessage {
} }
fn insert_private_key(instance: &str) -> Result<(), Box<dyn Error>> { fn insert_private_key(instance: &str) -> Result<(), Box<dyn Error>> {
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 cdata = load_cdata(instance)?;
let key = cdata.dh_privkey.ok_or("Missing private key")?; 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 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)?; 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); debug!("inserted private key into config: {:?}", config);
@ -63,8 +67,25 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter
}; };
info!("nebula process started"); 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 // dont need to save it, because we do not, in any circumstance, write to it
loop { 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() { match rx.try_recv() {
Ok(msg) => { Ok(msg) => {
match msg { match msg {
@ -113,6 +134,7 @@ pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter
return; return;
} }
}; };
last_restart_time = SystemTime::now();
debug!("nebula process restarted"); debug!("nebula process restarted");
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "trifid-pki" name = "trifid-pki"
version = "0.1.8" version = "0.1.9"
edition = "2021" edition = "2021"
description = "A rust implementation of the Nebula PKI system" description = "A rust implementation of the Nebula PKI system"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"

View File

@ -307,8 +307,8 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
if pem.tag != ED25519_PUBLIC_KEY_BANNER { if pem.tag != ED25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 64 { if pem.contents.len() != 32 {
return Err(KeyError::Not64Bytes.into()) return Err(KeyError::Not32Bytes.into())
} }
Ok(pem.contents) Ok(pem.contents)
} }
@ -324,8 +324,8 @@ pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box
if pem.tag != ED25519_PUBLIC_KEY_BANNER { if pem.tag != ED25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 64 { if pem.contents.len() != 32 {
return Err(KeyError::Not64Bytes.into()) return Err(KeyError::Not32Bytes.into())
} }
keys.push(pem.contents); keys.push(pem.contents);
} }

View File

@ -296,19 +296,20 @@ fn x25519_serialization() {
#[test] #[test]
fn ed25519_serialization() { fn ed25519_serialization() {
let bytes = [0u8; 64]; let bytes = [0u8; 64];
let bytes2 = [0u8; 32];
assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes); assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes);
assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes2)).unwrap(), bytes2);
assert!(deserialize_ed25519_public(&[0u8; 32]).is_err()); assert!(deserialize_ed25519_public(&[0u8; 64]).is_err());
let mut bytes = vec![]; let mut bytes = vec![];
bytes.append(&mut serialize_ed25519_public(&[0u8; 64])); bytes.append(&mut serialize_ed25519_public(&[0u8; 32]));
bytes.append(&mut serialize_ed25519_public(&[1u8; 64])); bytes.append(&mut serialize_ed25519_public(&[1u8; 32]));
let deser = deserialize_ed25519_public_many(&bytes).unwrap(); let deser = deserialize_ed25519_public_many(&bytes).unwrap();
assert_eq!(deser[0], [0u8; 64]); assert_eq!(deser[0], [0u8; 32]);
assert_eq!(deser[1], [1u8; 64]); 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(); deserialize_ed25519_public_many(&bytes).unwrap_err();
} }
@ -647,11 +648,11 @@ bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
#[test] #[test]
fn test_deserialize_ed25519_public() { fn test_deserialize_ed25519_public() {
let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- let pub_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA ED25519 PUBLIC KEY-----"; -----END NEBULA ED25519 PUBLIC KEY-----";
let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY----- let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA ED25519 PUBLIC KEY-----"; -----END NEBULA ED25519 PUBLIC KEY-----";
let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY----- let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
@ -660,7 +661,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PUBLIC KEY-----"; -END NEBULA ED25519 PUBLIC KEY-----";
deserialize_ed25519_public(priv_key).unwrap(); deserialize_ed25519_public(pub_key).unwrap();
deserialize_ed25519_public(short_key).unwrap_err(); deserialize_ed25519_public(short_key).unwrap_err();