tfclient enrollment successful

This commit is contained in:
c0repwn3r 2023-03-28 12:16:00 -04:00
parent 8a607733a3
commit b291d47459
Signed by: core
GPG Key ID: FDBF740DADDCEECF
10 changed files with 252 additions and 14 deletions

10
Cargo.lock generated
View File

@ -219,9 +219,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.23" version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
@ -1856,9 +1856,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.14" version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
dependencies = [ dependencies = [
"base64 0.21.0", "base64 0.21.0",
"bytes", "bytes",
@ -2420,6 +2420,8 @@ dependencies = [
name = "tfclient" name = "tfclient"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64 0.21.0",
"chrono",
"clap", "clap",
"ctrlc", "ctrlc",
"dirs 5.0.0", "dirs 5.0.0",

View File

@ -7,6 +7,9 @@ Connection: close
{"code":"xM22QsIzd4F0nLDTbh86RCSYwelfU_Hshqt-7u4yy_Y","dhPubkey":"LS0tLS1CRUdJTiBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQpqZW9aaDZZYUNNSHZKK04zWGRlQ1hCbHo3dm5saTBjL1NlQ1hVR3lYbEIwPQotLS0tLUVORCBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQo=","edPubkey":"LS0tLS1CRUdJTiBORUJVTEEgRUQyNTUxOSBQVUJMSUMgS0VZLS0tLS0KWHE0RG9mUGJoQzBubjc4VEhRWUxhNC83V1Ixei9iU1kzSm9pRzNRZ1VMcz0KLS0tLS1FTkQgTkVCVUxBIEVEMjU1MTkgUFVCTElDIEtFWS0tLS0tCg==","timestamp":"2023-02-01T13:24:56.380006369-05:00"} {"code":"xM22QsIzd4F0nLDTbh86RCSYwelfU_Hshqt-7u4yy_Y","dhPubkey":"LS0tLS1CRUdJTiBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQpqZW9aaDZZYUNNSHZKK04zWGRlQ1hCbHo3dm5saTBjL1NlQ1hVR3lYbEIwPQotLS0tLUVORCBORUJVTEEgWDI1NTE5IFBVQkxJQyBLRVktLS0tLQo=","edPubkey":"LS0tLS1CRUdJTiBORUJVTEEgRUQyNTUxOSBQVUJMSUMgS0VZLS0tLS0KWHE0RG9mUGJoQzBubjc4VEhRWUxhNC83V1Ixei9iU1kzSm9pRzNRZ1VMcz0KLS0tLS1FTkQgTkVCVUxBIEVEMjU1MTkgUFVCTElDIEtFWS0tLS0tCg==","timestamp":"2023-02-01T13:24:56.380006369-05:00"}
2023-02-01T13:24:56.380006369-05:00
%Y-%m-%dT%H:%M:%S.%f-%:z
HTTP/2 200 OK HTTP/2 200 OK
Cache-Control: no-store Cache-Control: no-store
Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none'

View File

@ -19,6 +19,9 @@ toml = "0.7.3"
serde = { version = "1.0.158", features = ["derive"] } serde = { version = "1.0.158", features = ["derive"] }
serde_json = "1.0.94" serde_json = "1.0.94"
ctrlc = "3.2.5" ctrlc = "3.2.5"
reqwest = { version = "0.11.16", features = ["blocking"] }
base64 = "0.21.0"
chrono = "0.4.24"
[build-dependencies] [build-dependencies]
serde = { version = "1.0.157", features = ["derive"] } serde = { version = "1.0.157", features = ["derive"] }

70
tfclient/src/api.rs Normal file
View File

@ -0,0 +1,70 @@
use std::error::Error;
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,
}
#[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<EnrollErrorSingular>
}
#[derive(Serialize, Deserialize)]
pub struct EnrollErrorSingular {
pub code: String,
pub message: String
}
pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<dyn Error>> {
let endpoint = server.join("/v2/enroll")?;
let client = Client::new();
let text = serde_json::to_string(request)?;
trace!("sending enroll: {}", text);
let resp = client.post(endpoint).body(text).send()?;
Ok(resp.json()?)
}

View File

@ -1,16 +1,24 @@
use std::sync::mpsc::{Receiver, TryRecvError}; use std::sync::mpsc::{Receiver, TryRecvError};
use log::{error, info}; use base64::Engine;
use chrono::Local;
use log::{error, info, warn};
use url::Url;
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::api::{APIResponse, enroll, EnrollRequest};
use crate::config::{load_cdata, save_cdata, TFClientConfig}; use crate::config::{load_cdata, save_cdata, TFClientConfig};
use crate::daemon::ThreadMessageSender; use crate::daemon::ThreadMessageSender;
pub enum APIWorkerMessage { pub enum APIWorkerMessage {
Shutdown Shutdown,
Enroll { code: String }
} }
pub fn apiworker_main(config: TFClientConfig, instance: 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();
// Generate dhPubkey and edPubkey if it doesn't exist // Generate dhPubkey and edPubkey if it doesn't exist
// Load vardata // Load vardata
let mut vdata = match load_cdata(&instance) { let mut vdata = match load_cdata(&instance) {
@ -58,6 +66,65 @@ pub fn apiworker_main(config: TFClientConfig, instance: String, _transmitters: T
APIWorkerMessage::Shutdown => { APIWorkerMessage::Shutdown => {
info!("recv on command socket: shutdown, stopping"); info!("recv on command socket: shutdown, stopping");
break; break;
},
APIWorkerMessage::Enroll { code } => {
info!("recv on command socket: enroll {}", code);
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_some() {
warn!("enrollment failed: already enrolled");
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);
info!("NebulaCAPool {}, org {} {}", resp.data.trusted_keys, resp.data.organization.name, resp.data.organization.id);
info!("Config: {}", resp.data.config);
cdata.host_id = Some(resp.data.host_id);
cdata.counter = resp.data.counter as i32;
cdata.ca_pool = Some(resp.data.trusted_keys);
cdata.org_name = Some(resp.data.organization.name);
cdata.org_id = Some(resp.data.organization.id);
cdata.config = Some(resp.data.config);
// Save vardata
match save_cdata(&instance, cdata) {
Ok(_) => (),
Err(e) => {
error!("Error saving cdata: {}", e);
error!("APIWorker exiting with error");
return;
}
}
} }
} }
}, },

View File

@ -17,7 +17,12 @@ pub struct TFClientConfig {
pub struct TFClientData { pub struct TFClientData {
pub host_id: Option<String>, pub host_id: Option<String>,
pub ed_privkey: Option<[u8; 32]>, pub ed_privkey: Option<[u8; 32]>,
pub dh_privkey: Option<[u8; 32]> pub dh_privkey: Option<[u8; 32]>,
pub counter: i32,
pub ca_pool: Option<String>,
pub org_id: Option<String>,
pub org_name: Option<String>,
pub config: Option<String>
} }
pub fn create_config(instance: &str) -> Result<(), Box<dyn Error>> { pub fn create_config(instance: &str) -> Result<(), Box<dyn Error>> {
@ -52,7 +57,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 }; let config = TFClientData { host_id: None, ed_privkey: None, dh_privkey: None, counter: 0, ca_pool: None, org_id: None, org_name: None, config: 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

@ -74,8 +74,9 @@ pub fn daemon_main(name: String, server: String) {
let config_api = config.clone(); let config_api = config.clone();
let transmitter_api = transmitter.clone(); let transmitter_api = transmitter.clone();
let name_api = name.clone(); let name_api = name.clone();
let server_api = server.clone();
let api_thread = thread::spawn(move || { let api_thread = thread::spawn(move || {
apiworker_main(config_api, name_api, transmitter_api, rx_api); apiworker_main(config_api, name_api, server_api,transmitter_api, rx_api);
}); });
info!("Starting Nebula thread..."); info!("Starting Nebula thread...");

View File

@ -23,6 +23,8 @@ pub mod config;
pub mod service; pub mod service;
pub mod apiworker; pub mod apiworker;
pub mod socketworker; pub mod socketworker;
pub mod api;
pub mod socketclient;
pub mod nebula_bin { pub mod nebula_bin {
include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs")); include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs"));
@ -37,6 +39,7 @@ use std::fs;
use clap::{Parser, ArgAction, Subcommand}; use clap::{Parser, ArgAction, Subcommand};
use log::{error, info}; use log::{error, info};
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use crate::config::load_config;
use crate::dirs::get_data_dir; use crate::dirs::get_data_dir;
use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert}; use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert};
use crate::service::entry::{cli_install, cli_start, cli_stop, cli_uninstall}; use crate::service::entry::{cli_install, cli_start, cli_stop, cli_uninstall};
@ -228,7 +231,23 @@ fn main() {
Commands::Run { name, server } => { Commands::Run { name, server } => {
daemon::daemon_main(name, server); daemon::daemon_main(name, server);
} }
Commands::Enroll { .. } => {} Commands::Enroll { name, code } => {
info!("Loading config...");
let config = match load_config(&name) {
Ok(cfg) => cfg,
Err(e) => {
error!("Error loading configuration: {}", e);
std::process::exit(1);
}
};
match socketclient::enroll(&code, &config) {
Ok(_) => (),
Err(e) => {
error!("Error sending enrollment request: {}", e);
std::process::exit(1);
}
};
}
} }
} }

View File

@ -0,0 +1,58 @@
use std::error::Error;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::net::{IpAddr, SocketAddr, TcpStream};
use log::{error, info};
use crate::config::TFClientConfig;
use crate::socketworker::{ctob, DisconnectReason, JSON_API_VERSION, JsonMessage};
pub fn enroll(code: &str, config: &TFClientConfig) -> Result<(), Box<dyn Error>> {
info!("Connecting to local command socket...");
let mut stream = TcpStream::connect(SocketAddr::new(IpAddr::from([127, 0, 0, 1]), config.listen_port))?;
let mut stream2 = stream.try_clone()?;
let mut reader = BufReader::new(&stream2);
info!("Sending Hello...");
stream.write_all(&ctob(JsonMessage::Hello {
version: JSON_API_VERSION,
}))?;
info!("Waiting for hello...");
let msg = read_msg(&mut reader)?;
match msg {
JsonMessage::Hello { .. } => {
info!("Server sent hello, connection established")
}
JsonMessage::Goodbye { reason } => {
error!("Disconnected by server. Reason: {:?}", reason);
return Err("Disconnected by server".into());
}
_ => {
error!("Server returned unexpected message: {:?}", msg);
error!("Sending goodbye and exiting");
stream.write_all(&ctob(JsonMessage::Goodbye {
reason: DisconnectReason::UnexpectedMessageType,
}))?;
return Err("Unexpected message type by server".into());
}
}
info!("Sending enroll request...");
stream.write_all(&ctob(JsonMessage::Enroll {
code: code.to_string(),
}))?;
info!("Sending disconnect...");
stream.write_all(&ctob(JsonMessage::Goodbye {
reason: DisconnectReason::Done,
}))?;
info!("Sent enroll request to tfclient daemon. Check logs to see if the enrollment was successful.");
Ok(())
}
fn read_msg(reader: &mut BufReader<&TcpStream>) -> Result<JsonMessage, Box<dyn Error>> {
let mut str = String::new();
reader.read_line(&mut str)?;
let msg: JsonMessage = serde_json::from_str(&str)?;
Ok(msg)
}

View File

@ -228,6 +228,12 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm
has_id: data.host_id.is_some(), has_id: data.host_id.is_some(),
id: data.host_id id: data.host_id
}))?; }))?;
},
JsonMessage::Enroll { code } => {
info!("Client sent enroll with code {}", code);
info!("Sending enroll request to apiworker");
transmitter.api_thread.send(APIWorkerMessage::Enroll { code }).unwrap();
} }
_ => { _ => {
@ -242,7 +248,7 @@ fn senthello_handle(client: &mut Client, transmitter: &ThreadMessageSender, comm
Ok(should_disconnect) Ok(should_disconnect)
} }
fn ctob(command: JsonMessage) -> Vec<u8> { pub fn ctob(command: JsonMessage) -> Vec<u8> {
let command_str = serde_json::to_string(&command).unwrap() + "\n"; let command_str = serde_json::to_string(&command).unwrap() + "\n";
command_str.into_bytes() command_str.into_bytes()
} }
@ -256,7 +262,7 @@ pub const JSON_API_VERSION: i32 = 1;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "method")] #[serde(tag = "method")]
enum JsonMessage { pub enum JsonMessage {
#[serde(rename = "hello")] #[serde(rename = "hello")]
Hello { Hello {
version: i32 version: i32
@ -273,12 +279,16 @@ enum JsonMessage {
HostID { HostID {
has_id: bool, has_id: bool,
id: Option<String> id: Option<String>
},
#[serde(rename = "enroll")]
Enroll {
code: String
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")] #[serde(tag = "type")]
enum DisconnectReason { pub enum DisconnectReason {
#[serde(rename = "unsupported_version")] #[serde(rename = "unsupported_version")]
UnsupportedVersion { expected: i32, got: i32 }, UnsupportedVersion { expected: i32, got: i32 },
#[serde(rename = "unexpected_message_type")] #[serde(rename = "unexpected_message_type")]