From 646340b637eccffe0e431f0c61926c6ae88227fe Mon Sep 17 00:00:00 2001 From: core Date: Thu, 23 Nov 2023 15:23:52 -0500 Subject: [PATCH] 0.3 error fixes, refmt --- dnapi-rs/src/client_blocking.rs | 5 +- docs/docs/trifid-api/api_support.md | 18 + nebula-ffi/build.rs | 32 +- nebula-ffi/src/lib.rs | 38 +- tfcli/src/account.rs | 141 ++++-- tfcli/src/api.rs | 6 +- tfcli/src/host.rs | 432 +++++++++++++----- tfcli/src/main.rs | 91 ++-- tfcli/src/network.rs | 39 +- tfcli/src/org.rs | 26 +- tfcli/src/role.rs | 188 ++++++-- tfclient/src/apiworker.rs | 10 +- tfclient/src/config.rs | 12 +- tfclient/src/daemon.rs | 9 +- tfclient/src/dirs.rs | 12 +- tfclient/src/main.rs | 3 +- tfclient/src/nebulaworker.rs | 101 ++-- tfclient/src/nebulaworker_inert.rs | 23 +- tfclient/src/timerworker.rs | 6 +- tfclient/src/util.rs | 38 +- trifid-api-old/src/codegen/mod.rs | 46 +- trifid-api-old/src/config.rs | 6 +- trifid-api-old/src/main.rs | 20 +- trifid-api-old/src/response.rs | 22 +- trifid-api-old/src/routes/v1/dnclient.rs | 43 +- trifid-api-old/src/routes/v1/hosts.rs | 382 ++++++++-------- trifid-api-old/src/routes/v1/roles.rs | 30 +- trifid-api-old/src/routes/v2/enroll.rs | 77 ++-- trifid-api-old/src/routes/v2/mod.rs | 2 +- trifid-api-old/src/routes/v2/whoami.rs | 53 +-- ...0815_225520_create_table_keystore_hosts.rs | 30 +- ...15_230218_create_table_keystore_entries.rs | 112 +++-- trifid-api/src/config.rs | 2 +- trifid-api/src/error.rs | 5 + trifid-api/src/models.rs | 6 +- trifid-api/src/response.rs | 24 +- trifid-api/src/routes/v1/auth/mod.rs | 2 +- trifid-api/src/routes/v1/auth/totp.rs | 87 ++-- .../src/routes/v1/auth/verify_magic_link.rs | 11 +- .../src/routes/v1/totp_authenticators.rs | 30 +- .../routes/v1/verify_totp_authenticator.rs | 99 ++-- 41 files changed, 1441 insertions(+), 878 deletions(-) create mode 100644 docs/docs/trifid-api/api_support.md diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index b2007ee..676d328 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -254,7 +254,10 @@ impl Client { ed_privkey.verify(b64_msg_bytes, &Signature::from_slice(&signature)?)?; debug!("signature valid via clientside check"); - debug!("signed with key: {:x?}", ed_privkey.verifying_key().as_bytes()); + debug!( + "signed with key: {:x?}", + ed_privkey.verifying_key().as_bytes() + ); let body = RequestV1 { version: 1, diff --git a/docs/docs/trifid-api/api_support.md b/docs/docs/trifid-api/api_support.md new file mode 100644 index 0000000..2ae0e26 --- /dev/null +++ b/docs/docs/trifid-api/api_support.md @@ -0,0 +1,18 @@ +# API Support + +This document is valid for: `trifid-api 0.3.0`. + +This document is only useful for developers, and it lists what endpoint versions are currently supported by trifid-api. This is subject to change at any time according to SemVer constraints. + +Endpoint types: +- **Documented** is an endpoint available in the official documentation +- **Reverse-engineered** is an endpoint that was reverse-engineered + +| Endpoint Name | Version | Endpoint | Type | Added In | +|---------------------------|---------|------------------------------------|--------------------|----------------| +| Signup | v1 | POST /v1/signup | Reverse-engineered | 0.3.0/79b1765e | +| Get Magic Link | v1 | POST /v1/auth/magic-link | Reverse-engineered | 0.3.0/52049947 | +| Verify Magic Link | v1 | POST /v1/auth/verify-magic-link | Reverse-engineered | 0.3.0/51b6d3a8 | +| Create TOTP Authenticator | v1 | POST /v1/totp-authenticators | Reverse-engineered | 0.3.0/4180bdd1 | +| Verify TOTP Authenticator | v1 | POST /v1/verify-totp-authenticator | Reverse-engineered | 0.3.0/19332e51 | +| Authenticate with TOTP | v1 | POST /v1/auth/totp | Reverse-engineered | 0.3.0/19332e51 | \ No newline at end of file diff --git a/nebula-ffi/build.rs b/nebula-ffi/build.rs index d6167fe..59fe846 100644 --- a/nebula-ffi/build.rs +++ b/nebula-ffi/build.rs @@ -1,7 +1,7 @@ -use std::{env, process}; -use std::path::PathBuf; use bindgen::CargoCallbacks; use std::path::Path; +use std::path::PathBuf; +use std::{env, process}; fn get_cargo_target_dir() -> Result> { let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); @@ -20,7 +20,6 @@ fn get_cargo_target_dir() -> Result String { "powerpc64" => "ppc64", "arm" => "arm", "aarch64" => "arm64", - arch => panic!("unsupported architecture {arch}") - }.to_string() + arch => panic!("unsupported architecture {arch}"), + } + .to_string() } fn goos() -> String { match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { @@ -139,8 +148,9 @@ fn goos() -> String { "dragonfly" => "dragonfly", "openbsd" => "openbsd", "netbsd" => "netbsd", - os => panic!("unsupported operating system {os}") - }.to_string() + os => panic!("unsupported operating system {os}"), + } + .to_string() } fn print_link() { @@ -157,4 +167,4 @@ fn link_type() -> String { } else { "c-archive".to_string() } -} \ No newline at end of file +} diff --git a/nebula-ffi/src/lib.rs b/nebula-ffi/src/lib.rs index f16e16b..9001ce4 100644 --- a/nebula-ffi/src/lib.rs +++ b/nebula-ffi/src/lib.rs @@ -25,7 +25,6 @@ #![deny(clippy::missing_panics_doc)] #![deny(clippy::missing_safety_doc)] - #[allow(non_upper_case_globals)] #[allow(non_camel_case_types)] #[allow(non_snake_case)] @@ -36,12 +35,11 @@ pub mod generated { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } +use generated::GoString; use std::error::Error; use std::ffi::{c_char, CString}; use std::fmt::{Display, Formatter}; -use std::path::{Path}; -use generated::GoString; - +use std::path::Path; impl From<&str> for GoString { #[allow(clippy::cast_possible_wrap)] @@ -51,7 +49,7 @@ impl From<&str> for GoString { let ptr = c_str.as_ptr(); let go_string = GoString { p: ptr, - n: c_str.as_bytes().len() as isize + n: c_str.as_bytes().len() as isize, }; go_string } @@ -73,14 +71,18 @@ impl NebulaInstance { /// # Panics /// This function will panic if memory is corrupted while communicating with Go. pub fn new(config_path: &Path, config_test: bool) -> Result> { - let mut config_path_bytes = unsafe { config_path.display().to_string().as_bytes_mut().to_vec() }; + let mut config_path_bytes = + unsafe { config_path.display().to_string().as_bytes_mut().to_vec() }; config_path_bytes.push(0u8); let config_test_u8 = u8::from(config_test); let res; unsafe { - res = generated::NebulaSetup(config_path_bytes.as_mut_ptr().cast::(), config_test_u8); + res = generated::NebulaSetup( + config_path_bytes.as_mut_ptr().cast::(), + config_test_u8, + ); } let res = cstring_to_string(res); @@ -194,18 +196,18 @@ pub enum NebulaError { /// Returned by nebula when the TUN/TAP device already exists DeviceOrResourceBusy { /// The complete error string returned by the Nebula wrapper - error_str: String + error_str: String, }, /// An unknown error that the error parser couldn't figure out how to parse. Unknown { /// The complete error string returned by the Nebula wrapper - error_str: String + error_str: String, }, /// Occurs if you call a function before NebulaSetup has been called NebulaNotSetup { /// The complete error string returned by the Nebula wrapper - error_str: String - } + error_str: String, + }, } impl Display for NebulaError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -223,11 +225,17 @@ impl NebulaError { #[must_use] pub fn from_string(string: &str) -> Self { if string.starts_with("device or resource busy") { - Self::DeviceOrResourceBusy { error_str: string.to_string() } + Self::DeviceOrResourceBusy { + error_str: string.to_string(), + } } else if string.starts_with("NebulaSetup has not yet been called") { - Self::NebulaNotSetup { error_str: string.to_string() } + Self::NebulaNotSetup { + error_str: string.to_string(), + } } else { - Self::Unknown { error_str: string.to_string() } + Self::Unknown { + error_str: string.to_string(), + } } } -} \ No newline at end of file +} diff --git a/tfcli/src/account.rs b/tfcli/src/account.rs index edd5b7a..7fdb285 100644 --- a/tfcli/src/account.rs +++ b/tfcli/src/account.rs @@ -1,38 +1,48 @@ +use crate::api::APIErrorResponse; +use crate::AccountCommands; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::fs; -use serde::{Deserialize, Serialize}; use url::Url; -use crate::AccountCommands; -use crate::api::APIErrorResponse; pub async fn account_main(command: AccountCommands, server: Url) -> Result<(), Box> { match command { AccountCommands::Create { email } => create_account(email, server).await, - AccountCommands::MagicLink { magic_link_token } => auth_magic_link(magic_link_token, server).await, + AccountCommands::MagicLink { magic_link_token } => { + auth_magic_link(magic_link_token, server).await + } AccountCommands::MfaSetup {} => create_mfa_authenticator(server).await, - AccountCommands::MfaSetupFinish {code, token} => finish_mfa_authenticator(token, code, server).await, - AccountCommands::Mfa {code} => mfa_auth(code, server).await, - AccountCommands::Login { email } => login_account(email, server).await + AccountCommands::MfaSetupFinish { code, token } => { + finish_mfa_authenticator(token, code, server).await + } + AccountCommands::Mfa { code } => mfa_auth(code, server).await, + AccountCommands::Login { email } => login_account(email, server).await, } } #[derive(Serialize)] pub struct CreateAccountBody { - pub email: String + pub email: String, } pub async fn create_account(email: String, server: Url) -> Result<(), Box> { let client = reqwest::Client::new(); - let res = client.post(server.join("/v1/signup")?).json(&CreateAccountBody { email }).send().await?; + let res = client + .post(server.join("/v1/signup")?) + .json(&CreateAccountBody { email }) + .send() + .await?; if res.status().is_success() { println!("Account created successfully, check your email."); println!("Finish creating your account with 'tfcli account magic-link --magic-link-token [magic-link-token]'."); } else { - let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error creating account: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error creating account: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -42,21 +52,27 @@ pub async fn create_account(email: String, server: Url) -> Result<(), Box Result<(), Box> { let client = reqwest::Client::new(); - let res = client.post(server.join("/v1/auth/magic-link")?).json(&LoginAccountBody { email }).send().await?; + let res = client + .post(server.join("/v1/auth/magic-link")?) + .json(&LoginAccountBody { email }) + .send() + .await?; if res.status().is_success() { println!("Magic link sent, check your email."); println!("Finish creating your account with 'tfcli account magic-link --magic-link-token [magic-link-token]'."); } else { - let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error logging in: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error logging in: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -64,26 +80,31 @@ pub async fn login_account(email: String, server: Url) -> Result<(), Box Result<(), Box> { let client = reqwest::Client::new(); - let res = client.post(server.join("/v1/auth/verify-magic-link")?).json(&MagicLinkBody { magic_link_token: magic_token }).send().await?; + let res = client + .post(server.join("/v1/auth/verify-magic-link")?) + .json(&MagicLinkBody { + magic_link_token: magic_token, + }) + .send() + .await?; if res.status().is_success() { let resp: MagicLinkSuccess = res.json().await?; @@ -97,7 +118,10 @@ pub async fn auth_magic_link(magic_token: String, server: Url) -> Result<(), Box } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error getting session token: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error getting session token: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -135,14 +159,14 @@ pub struct WhoamiResponseMetadata {} #[derive(Deserialize)] pub struct CreateMfaResponse { - pub data: CreateMfaResponseData + pub data: CreateMfaResponseData, } #[derive(Deserialize)] pub struct CreateMfaResponseData { #[serde(rename = "totpToken")] pub totp_token: String, pub secret: String, - pub url: String + pub url: String, } pub async fn create_mfa_authenticator(server: Url) -> Result<(), Box> { @@ -153,14 +177,25 @@ pub async fn create_mfa_authenticator(server: Url) -> Result<(), Box> let session_token = fs::read_to_string(&token_store)?; // do we have mfa already? - let whoami: WhoamiResponse = client.get(server.join("/v2/whoami")?).bearer_auth(&session_token).send().await?.json().await?; + let whoami: WhoamiResponse = client + .get(server.join("/v2/whoami")?) + .bearer_auth(&session_token) + .send() + .await? + .json() + .await?; if whoami.data.actor.has_totp_authenticator { eprintln!("[error] user already has a totp authenticator, cannot add another one"); std::process::exit(1); } - let res = client.post(server.join("/v1/totp-authenticators")?).bearer_auth(&session_token).body("{}").send().await?; + let res = client + .post(server.join("/v1/totp-authenticators")?) + .bearer_auth(&session_token) + .body("{}") + .send() + .await?; if res.status().is_success() { let resp: CreateMfaResponse = res.json().await?; @@ -169,14 +204,23 @@ pub async fn create_mfa_authenticator(server: Url) -> Result<(), Box> println!("To complete setup, you'll need a TOTP-compatible app, such as Google Authenticator or Authy."); println!("Scan the following code with your authenticator app:"); qr2term::print_qr(resp.data.url)?; - println!("Alternatively, enter the following secret into your authenticator app: '{}'", resp.data.secret); + println!( + "Alternatively, enter the following secret into your authenticator app: '{}'", + resp.data.secret + ); println!("Once done, enable TOTP by running the following command with the code shown on your authenticator app:"); - println!("tfcli account mfa-setup-finish --token {} --code [CODE IN AUTHENTICATOR]", resp.data.totp_token); + println!( + "tfcli account mfa-setup-finish --token {} --code [CODE IN AUTHENTICATOR]", + resp.data.totp_token + ); println!("This code will expire in 10 minutes."); } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error adding MFA to account: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error adding MFA to account: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -188,27 +232,39 @@ pub async fn create_mfa_authenticator(server: Url) -> Result<(), Box> pub struct MfaVerifyBody { #[serde(rename = "totpToken")] pub totp_token: String, - pub code: String + pub code: String, } #[derive(Deserialize)] pub struct MFASuccess { - pub data: MFASuccessBody + pub data: MFASuccessBody, } #[derive(Deserialize)] pub struct MFASuccessBody { #[serde(rename = "authToken")] - pub auth_token: String + pub auth_token: String, } -pub async fn finish_mfa_authenticator(token: String, code: String, server: Url) -> Result<(), Box> { +pub async fn finish_mfa_authenticator( + token: String, + code: String, + server: Url, +) -> Result<(), Box> { let client = reqwest::Client::new(); // load session token let token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&token_store)?; - let res = client.post(server.join("/v1/verify-totp-authenticators")?).json(&MfaVerifyBody {totp_token: token, code }).bearer_auth(session_token).send().await?; + let res = client + .post(server.join("/v1/verify-totp-authenticators")?) + .json(&MfaVerifyBody { + totp_token: token, + code, + }) + .bearer_auth(session_token) + .send() + .await?; if res.status().is_success() { let resp: MFASuccess = res.json().await?; @@ -222,7 +278,10 @@ pub async fn finish_mfa_authenticator(token: String, code: String, server: Url) } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error verifying MFA code: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error verifying MFA code: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -232,7 +291,7 @@ pub async fn finish_mfa_authenticator(token: String, code: String, server: Url) #[derive(Serialize)] pub struct MfaAuthBody { - pub code: String + pub code: String, } pub async fn mfa_auth(code: String, server: Url) -> Result<(), Box> { @@ -242,7 +301,12 @@ pub async fn mfa_auth(code: String, server: Url) -> Result<(), Box> { let token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&token_store)?; - let res = client.post(server.join("/v1/auth/totp")?).json(&MfaAuthBody { code }).bearer_auth(session_token).send().await?; + let res = client + .post(server.join("/v1/auth/totp")?) + .json(&MfaAuthBody { code }) + .bearer_auth(session_token) + .send() + .await?; if res.status().is_success() { let resp: MFASuccess = res.json().await?; @@ -256,7 +320,10 @@ pub async fn mfa_auth(code: String, server: Url) -> Result<(), Box> { } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error verifying MFA code: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error verifying MFA code: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } diff --git a/tfcli/src/api.rs b/tfcli/src/api.rs index 5f9f913..8cc16cc 100644 --- a/tfcli/src/api.rs +++ b/tfcli/src/api.rs @@ -2,11 +2,11 @@ use serde::Deserialize; #[derive(Deserialize)] pub struct APIErrorResponse { - pub errors: Vec + pub errors: Vec, } #[derive(Deserialize)] pub struct APIError { pub code: String, pub message: String, - pub path: Option -} \ No newline at end of file + pub path: Option, +} diff --git a/tfcli/src/host.rs b/tfcli/src/host.rs index 116edf6..2f8d68d 100644 --- a/tfcli/src/host.rs +++ b/tfcli/src/host.rs @@ -1,34 +1,68 @@ +use crate::api::APIErrorResponse; +use crate::{HostCommands, HostOverrideCommands}; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::fs; use std::net::{Ipv4Addr, SocketAddrV4}; -use serde::{Deserialize, Serialize}; -use url::{Url}; -use crate::api::APIErrorResponse; -use crate::{HostCommands, HostOverrideCommands}; +use url::Url; pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box> { match command { HostCommands::List {} => list_hosts(server).await, - HostCommands::Create { name, network_id, role_id, ip_address, listen_port, lighthouse, relay, static_address } => create_host(name, network_id, role_id, ip_address, listen_port, lighthouse, relay, static_address, server).await, + HostCommands::Create { + name, + network_id, + role_id, + ip_address, + listen_port, + lighthouse, + relay, + static_address, + } => { + create_host( + name, + network_id, + role_id, + ip_address, + listen_port, + lighthouse, + relay, + static_address, + server, + ) + .await + } HostCommands::Lookup { id } => get_host(id, server).await, HostCommands::Delete { id } => delete_host(id, server).await, - HostCommands::Update { id, listen_port, static_address, name, ip, role } => update_host(id, listen_port, static_address, name, ip, role, server).await, + HostCommands::Update { + id, + listen_port, + static_address, + name, + ip, + role, + } => update_host(id, listen_port, static_address, name, ip, role, server).await, HostCommands::Block { id } => block_host(id, server).await, HostCommands::Enroll { id } => enroll_host(id, server).await, HostCommands::Overrides { command } => match command { HostOverrideCommands::List { id } => list_overrides(id, server).await, - HostOverrideCommands::Set { id, key, boolean, string, numeric } => set_override(id, key, boolean, numeric, string, server).await, - HostOverrideCommands::Unset { id, key } => unset_override(id, key, server).await - } + HostOverrideCommands::Set { + id, + key, + boolean, + string, + numeric, + } => set_override(id, key, boolean, numeric, string, server).await, + HostOverrideCommands::Unset { id, key } => unset_override(id, key, server).await, + }, } } #[derive(Deserialize)] pub struct HostListResp { - pub data: Vec + pub data: Vec, } - #[derive(Serialize, Deserialize)] pub struct HostMetadata { #[serde(rename = "lastSeenAt")] @@ -77,7 +111,11 @@ pub async fn list_hosts(server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join("/v1/hosts?pageSize=5000")?).bearer_auth(token).send().await?; + let res = client + .get(server.join("/v1/hosts?pageSize=5000")?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let resp: HostListResp = res.json().await?; @@ -88,14 +126,33 @@ pub async fn list_hosts(server: Url) -> Result<(), Box> { println!(" Network: {}", host.network_id); println!(" Role: {}", host.role_id); println!(" IP Address: {}", host.ip_address); - println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::>().join(", ")); + println!( + " Static Addresses: {}", + host.static_addresses + .iter() + .map(|u| u.to_string()) + .collect::>() + .join(", ") + ); println!(" Listen Port: {}", host.listen_port); - println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } ); + println!( + " Type: {}", + if host.is_lighthouse { + "Lighthouse" + } else if host.is_relay { + "Relay" + } else { + "Host" + } + ); println!(" Blocked: {}", host.is_blocked); println!(" Last Seen: {}", host.metadata.last_seen_at); println!(" Client Version: {}", host.metadata.version); println!(" Platform: {}", host.metadata.platform); - println!("Client Update Available: {}", host.metadata.update_available); + println!( + "Client Update Available: {}", + host.metadata.update_available + ); println!(" Created: {}", host.created_at); println!(); } @@ -106,7 +163,10 @@ pub async fn list_hosts(server: Url) -> Result<(), Box> { } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing hosts: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing hosts: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -114,7 +174,6 @@ pub async fn list_hosts(server: Url) -> Result<(), Box> { Ok(()) } - #[derive(Serialize, Deserialize)] pub struct HostCreateBody { pub name: String, @@ -134,11 +193,9 @@ pub struct HostCreateBody { pub static_addresses: Vec, } - #[derive(Serialize, Deserialize)] pub struct HostGetMetadata {} - #[derive(Serialize, Deserialize)] pub struct HostGetResponse { pub data: Host, @@ -146,7 +203,17 @@ pub struct HostGetResponse { } #[allow(clippy::too_many_arguments)] -pub async fn create_host(name: String, network_id: String, role_id: String, ip_address: Ipv4Addr, listen_port: Option, lighthouse: bool, relay: bool, static_address: Option, server: Url) -> Result<(), Box> { +pub async fn create_host( + name: String, + network_id: String, + role_id: String, + ip_address: Ipv4Addr, + listen_port: Option, + lighthouse: bool, + relay: bool, + static_address: Option, + server: Url, +) -> Result<(), Box> { if lighthouse && relay { eprintln!("[error] Error creating host: a host cannot be both a lighthouse and a relay at the same time"); std::process::exit(1); @@ -172,16 +239,21 @@ pub async fn create_host(name: String, network_id: String, role_id: String, ip_a let token = format!("{} {}", session_token, auth_token); - let res = client.post(server.join("/v1/hosts")?).json(&HostCreateBody { - name, - network_id, - role_id, - ip_address, - listen_port: listen_port.unwrap_or(0), - is_lighthouse: lighthouse, - is_relay: relay, - static_addresses: static_address.map_or(vec![], |u| vec![u]), - }).bearer_auth(token).send().await?; + let res = client + .post(server.join("/v1/hosts")?) + .json(&HostCreateBody { + name, + network_id, + role_id, + ip_address, + listen_port: listen_port.unwrap_or(0), + is_lighthouse: lighthouse, + is_relay: relay, + static_addresses: static_address.map_or(vec![], |u| vec![u]), + }) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let host: Host = res.json::().await?.data; @@ -191,21 +263,42 @@ pub async fn create_host(name: String, network_id: String, role_id: String, ip_a println!(" Network: {}", host.network_id); println!(" Role: {}", host.role_id); println!(" IP Address: {}", host.ip_address); - println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::>().join(", ")); + println!( + " Static Addresses: {}", + host.static_addresses + .iter() + .map(|u| u.to_string()) + .collect::>() + .join(", ") + ); println!(" Listen Port: {}", host.listen_port); - println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } ); + println!( + " Type: {}", + if host.is_lighthouse { + "Lighthouse" + } else if host.is_relay { + "Relay" + } else { + "Host" + } + ); println!(" Blocked: {}", host.is_blocked); println!(" Last Seen: {}", host.metadata.last_seen_at); println!(" Client Version: {}", host.metadata.version); println!(" Platform: {}", host.metadata.platform); - println!("Client Update Available: {}", host.metadata.update_available); + println!( + "Client Update Available: {}", + host.metadata.update_available + ); println!(" Created: {}", host.created_at); println!(); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error creating host: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error creating host: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -224,7 +317,11 @@ pub async fn get_host(id: String, server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join(&format!("/v1/hosts/{}", id))?).bearer_auth(token).send().await?; + let res = client + .get(server.join(&format!("/v1/hosts/{}", id))?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let host: Host = res.json::().await?.data; @@ -234,21 +331,42 @@ pub async fn get_host(id: String, server: Url) -> Result<(), Box> { println!(" Network: {}", host.network_id); println!(" Role: {}", host.role_id); println!(" IP Address: {}", host.ip_address); - println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::>().join(", ")); + println!( + " Static Addresses: {}", + host.static_addresses + .iter() + .map(|u| u.to_string()) + .collect::>() + .join(", ") + ); println!(" Listen Port: {}", host.listen_port); - println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } ); + println!( + " Type: {}", + if host.is_lighthouse { + "Lighthouse" + } else if host.is_relay { + "Relay" + } else { + "Host" + } + ); println!(" Blocked: {}", host.is_blocked); println!(" Last Seen: {}", host.metadata.last_seen_at); println!(" Client Version: {}", host.metadata.version); println!(" Platform: {}", host.metadata.platform); - println!("Client Update Available: {}", host.metadata.update_available); + println!( + "Client Update Available: {}", + host.metadata.update_available + ); println!(" Created: {}", host.created_at); println!(); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing hosts: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing hosts: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -267,14 +385,21 @@ pub async fn delete_host(id: String, server: Url) -> Result<(), Box> let token = format!("{} {}", session_token, auth_token); - let res = client.delete(server.join(&format!("/v1/hosts/{}", id))?).bearer_auth(token).send().await?; + let res = client + .delete(server.join(&format!("/v1/hosts/{}", id))?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { println!("Host removed"); } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error removing host: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error removing host: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -290,10 +415,18 @@ pub struct HostUpdateBody { pub static_addresses: Vec, pub name: Option, pub ip: Option, - pub role: Option + pub role: Option, } -pub async fn update_host(id: String, listen_port: Option, static_address: Option, name: Option, ip: Option, role: Option, server: Url) -> Result<(), Box> { +pub async fn update_host( + id: String, + listen_port: Option, + static_address: Option, + name: Option, + ip: Option, + role: Option, + server: Url, +) -> Result<(), Box> { let client = reqwest::Client::new(); // load session token @@ -304,13 +437,18 @@ pub async fn update_host(id: String, listen_port: Option, static_address: O let token = format!("{} {}", session_token, auth_token); - let res = client.put(server.join(&format!("/v1/hosts/{}?extension=extended_hosts", id))?).json(&HostUpdateBody { - listen_port: listen_port.unwrap_or(0), - static_addresses: static_address.map_or_else(Vec::new, |u| vec![u]), - name, - ip, - role - }).bearer_auth(token).send().await?; + let res = client + .put(server.join(&format!("/v1/hosts/{}?extension=extended_hosts", id))?) + .json(&HostUpdateBody { + listen_port: listen_port.unwrap_or(0), + static_addresses: static_address.map_or_else(Vec::new, |u| vec![u]), + name, + ip, + role, + }) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let host: Host = res.json::().await?.data; @@ -320,21 +458,42 @@ pub async fn update_host(id: String, listen_port: Option, static_address: O println!(" Network: {}", host.network_id); println!(" Role: {}", host.role_id); println!(" IP Address: {}", host.ip_address); - println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::>().join(", ")); + println!( + " Static Addresses: {}", + host.static_addresses + .iter() + .map(|u| u.to_string()) + .collect::>() + .join(", ") + ); println!(" Listen Port: {}", host.listen_port); - println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } ); + println!( + " Type: {}", + if host.is_lighthouse { + "Lighthouse" + } else if host.is_relay { + "Relay" + } else { + "Host" + } + ); println!(" Blocked: {}", host.is_blocked); println!(" Last Seen: {}", host.metadata.last_seen_at); println!(" Client Version: {}", host.metadata.version); println!(" Platform: {}", host.metadata.platform); - println!("Client Update Available: {}", host.metadata.update_available); + println!( + "Client Update Available: {}", + host.metadata.update_available + ); println!(" Created: {}", host.created_at); println!(); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error updating host: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error updating host: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -342,7 +501,6 @@ pub async fn update_host(id: String, listen_port: Option, static_address: O Ok(()) } - #[derive(Serialize, Deserialize)] pub struct EnrollmentCodeResponseMetadata {} @@ -376,18 +534,29 @@ pub async fn enroll_host(id: String, server: Url) -> Result<(), Box> let token = format!("{} {}", session_token, auth_token); - let res = client.post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?).header("content-length", 0).bearer_auth(token).send().await?; + let res = client + .post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?) + .header("content-length", 0) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let resp: EnrollmentResponse = res.json().await?; - println!("Enrollment code generated. Enroll the host with the following code: {}", resp.data.enrollment_code.code); + println!( + "Enrollment code generated. Enroll the host with the following code: {}", + resp.data.enrollment_code.code + ); println!("This code will be valid for {} seconds, at which point you will need to generate a new code", resp.data.enrollment_code.lifetime_seconds); println!("If this host is blocked, a successful re-enrollment will unblock it."); } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error blocking host: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error blocking host: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -406,14 +575,22 @@ pub async fn block_host(id: String, server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.post(server.join(&format!("/v1/hosts/{}/block", id))?).header("Content-Length", "0").bearer_auth(token).send().await?; + let res = client + .post(server.join(&format!("/v1/hosts/{}/block", id))?) + .header("Content-Length", "0") + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { println!("Host blocked. To unblock it, re-enroll the host."); } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error blocking host: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error blocking host: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -423,18 +600,18 @@ pub async fn block_host(id: String, server: Url) -> Result<(), Box> { #[derive(Serialize, Deserialize)] pub struct HostConfigOverrideResponse { - pub data: HostConfigOverrideData + pub data: HostConfigOverrideData, } #[derive(Serialize, Deserialize)] pub struct HostConfigOverrideData { - pub overrides: Vec + pub overrides: Vec, } #[derive(Serialize, Deserialize)] pub struct HostConfigOverrideDataOverride { pub key: String, - pub value: HostConfigOverrideDataOverrideValue + pub value: HostConfigOverrideDataOverrideValue, } #[derive(Serialize, Deserialize)] @@ -442,7 +619,7 @@ pub struct HostConfigOverrideDataOverride { pub enum HostConfigOverrideDataOverrideValue { Boolean(bool), Numeric(i64), - Other(String) + Other(String), } pub async fn list_overrides(id: String, server: Url) -> Result<(), Box> { @@ -456,18 +633,25 @@ pub async fn list_overrides(id: String, server: Url) -> Result<(), Box format!("bool:{}", v), - HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), - HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v) - }); + println!( + "Value: {}", + match &c_override.value { + HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v), + HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), + HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v), + } + ); } if resp.data.overrides.is_empty() { @@ -476,7 +660,10 @@ pub async fn list_overrides(id: String, server: Url) -> Result<(), Box Result<(), Box + pub overrides: Vec, } -pub async fn set_override(id: String, key: String, boolean: Option, numeric: Option, other: Option, server: Url) -> Result<(), Box> { +pub async fn set_override( + id: String, + key: String, + boolean: Option, + numeric: Option, + other: Option, + server: Url, +) -> Result<(), Box> { if boolean.is_none() && numeric.is_none() && other.is_none() { eprintln!("[error] no value provided: you must provide at least --boolean, --numeric, or --string"); std::process::exit(1); - } else if boolean.is_some() && numeric.is_some() || boolean.is_some() && other.is_some() || numeric.is_some() && other.is_some() { + } else if boolean.is_some() && numeric.is_some() + || boolean.is_some() && other.is_some() + || numeric.is_some() && other.is_some() + { eprintln!("[error] multiple values provided: you must provide only one of --boolean, --numeric, or --string"); std::process::exit(1); } @@ -520,7 +717,11 @@ pub async fn set_override(id: String, key: String, boolean: Option, numeri let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?; + let res = client + .get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?) + .bearer_auth(token.clone()) + .send() + .await?; if res.status().is_success() { let resp: HostConfigOverrideResponse = res.json().await?; @@ -533,25 +734,28 @@ pub async fn set_override(id: String, key: String, boolean: Option, numeri } } - others.push(HostConfigOverrideDataOverride { - key, - value: val, - }); + others.push(HostConfigOverrideDataOverride { key, value: val }); - let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest { - overrides: others, - }).send().await?; + let res = client + .put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?) + .bearer_auth(token.clone()) + .json(&SetOverrideRequest { overrides: others }) + .send() + .await?; if res.status().is_success() { let resp: HostConfigOverrideResponse = res.json().await?; for c_override in &resp.data.overrides { println!(" Key: {}", c_override.key); - println!("Value: {}", match &c_override.value { - HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v), - HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), - HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v) - }); + println!( + "Value: {}", + match &c_override.value { + HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v), + HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), + HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v), + } + ); } if resp.data.overrides.is_empty() { @@ -562,14 +766,20 @@ pub async fn set_override(id: String, key: String, boolean: Option, numeri } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error setting config overrides: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error setting config overrides: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -588,7 +798,11 @@ pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?; + let res = client + .get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?) + .bearer_auth(token.clone()) + .send() + .await?; if res.status().is_success() { let resp: HostConfigOverrideResponse = res.json().await?; @@ -601,20 +815,26 @@ pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), } } - let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest { - overrides: others, - }).send().await?; + let res = client + .put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?) + .bearer_auth(token.clone()) + .json(&SetOverrideRequest { overrides: others }) + .send() + .await?; if res.status().is_success() { let resp: HostConfigOverrideResponse = res.json().await?; for c_override in &resp.data.overrides { println!(" Key: {}", c_override.key); - println!("Value: {}", match &c_override.value { - HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v), - HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), - HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v) - }); + println!( + "Value: {}", + match &c_override.value { + HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v), + HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v), + HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v), + } + ); } if resp.data.overrides.is_empty() { @@ -625,14 +845,20 @@ pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error unsetting config overrides: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error unsetting config overrides: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } diff --git a/tfcli/src/main.rs b/tfcli/src/main.rs index a4eba80..9f36e97 100644 --- a/tfcli/src/main.rs +++ b/tfcli/src/main.rs @@ -1,21 +1,21 @@ -use std::error::Error; -use std::fs; -use std::net::{Ipv4Addr, SocketAddrV4}; -use clap::{Parser, Subcommand}; -use ipnet::Ipv4Net; -use url::Url; use crate::account::account_main; use crate::host::host_main; use crate::network::network_main; use crate::org::org_main; use crate::role::role_main; +use clap::{Parser, Subcommand}; +use ipnet::Ipv4Net; +use std::error::Error; +use std::fs; +use std::net::{Ipv4Addr, SocketAddrV4}; +use url::Url; pub mod account; pub mod api; +pub mod host; pub mod network; pub mod org; pub mod role; -pub mod host; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -25,7 +25,7 @@ pub struct Args { command: Commands, #[clap(short, long, env = "TFCLI_SERVER")] /// The base URL of your trifid-api-old instance. Defaults to the value in $XDG_CONFIG_HOME/tfcli-server-url.conf or the TFCLI_SERVER environment variable. - server: Option + server: Option, } #[derive(Subcommand, Debug)] @@ -33,28 +33,28 @@ pub enum Commands { /// Manage your trifid account Account { #[command(subcommand)] - command: AccountCommands + command: AccountCommands, }, /// Manage the networks associated with your trifid account Network { #[command(subcommand)] - command: NetworkCommands + command: NetworkCommands, }, /// Manage the organization associated with your trifid account Org { #[command(subcommand)] - command: OrgCommands + command: OrgCommands, }, /// Manage the roles associated with your trifid organization Role { #[command(subcommand)] - command: RoleCommands + command: RoleCommands, }, /// Manage the hosts associated with your trifid network Host { #[command(subcommand)] - command: HostCommands - } + command: HostCommands, + }, } #[derive(Subcommand, Debug)] @@ -62,17 +62,17 @@ pub enum AccountCommands { /// Create a new trifid account on the designated server Create { #[clap(short, long)] - email: String + email: String, }, /// Log into an existing account on the designated server Login { #[clap(short, long)] - email: String + email: String, }, /// Log in to your account with a magic-link token acquired via email or the trifid-api-old logs. MagicLink { #[clap(short, long)] - magic_link_token: String + magic_link_token: String, }, /// Create a new TOTP authenticator on this account to enable authorizing with 2fa and performing all management tasks. MfaSetup {}, @@ -81,13 +81,13 @@ pub enum AccountCommands { #[clap(short, long)] code: String, #[clap(short, long)] - token: String + token: String, }, /// Create a new short-lived authentication token by inputting the code shown on your authenticator app. Mfa { #[clap(short, long)] - code: String - } + code: String, + }, } #[derive(Subcommand, Debug)] @@ -97,8 +97,8 @@ pub enum NetworkCommands { /// Lookup a specific network by ID. Lookup { #[clap(short, long)] - id: String - } + id: String, + }, } #[derive(Subcommand, Debug)] @@ -106,8 +106,8 @@ pub enum OrgCommands { /// Create an organization on your trifid-api-old server. NOTE: This command ONLY works on trifid-api-old servers. It will NOT work on original DN servers. Create { #[clap(short, long)] - cidr: Ipv4Net - } + cidr: Ipv4Net, + }, } #[derive(Subcommand, Debug)] @@ -120,19 +120,19 @@ pub enum RoleCommands { description: String, /// A JSON string containing the firewall rules to add to this host #[clap(short, long)] - rules_json: String + rules_json: String, }, /// List all roles attached to your organization List {}, /// Lookup a specific role by it's ID Lookup { #[clap(short, long)] - id: String + id: String, }, /// Delete a specific role by it's ID Delete { #[clap(short, long)] - id: String + id: String, }, /// Update a specific role by it's ID. Warning: any data not provided in this update will be removed - include all data you wish to remain Update { @@ -142,8 +142,8 @@ pub enum RoleCommands { description: String, /// A JSON string containing the firewall rules to add to this host #[clap(short, long)] - rules_json: String - } + rules_json: String, + }, } #[derive(Subcommand, Debug)] @@ -165,19 +165,19 @@ pub enum HostCommands { #[clap(short = 'R', long)] relay: bool, #[clap(short, long)] - static_address: Option + static_address: Option, }, /// List all hosts on your network List {}, /// Lookup a specific host by it's ID Lookup { #[clap(short, long)] - id: String + id: String, }, /// Delete a specific host by it's ID Delete { #[clap(short, long)] - id: String + id: String, }, /// Update a specific host by it's ID, changing the listen port and static addresses, as well as the name, ip and role. The name, ip and role updates will only work on trifid-api-old compatible servers. Update { @@ -192,23 +192,23 @@ pub enum HostCommands { #[clap(short, long)] role: Option, #[clap(short = 'I', long)] - ip: Option + ip: Option, }, /// Blocks the specified host from the network Block { #[clap(short, long)] - id: String + id: String, }, /// Enroll or re-enroll the host by generating an enrollment code Enroll { #[clap(short, long)] - id: String + id: String, }, /// Manage config overrides set on the host Overrides { #[command(subcommand)] - command: HostOverrideCommands - } + command: HostOverrideCommands, + }, } #[derive(Subcommand, Debug)] @@ -216,7 +216,7 @@ pub enum HostOverrideCommands { /// List the config overrides set on the host List { #[clap(short, long)] - id: String + id: String, }, /// Set a config override on the host Set { @@ -229,15 +229,15 @@ pub enum HostOverrideCommands { #[clap(short, long)] numeric: Option, #[clap(short, long)] - string: Option + string: Option, }, /// Unset a config override on the host Unset { #[clap(short, long)] id: String, #[clap(short, long)] - key: String - } + key: String, + }, } #[tokio::main] @@ -270,7 +270,10 @@ async fn main2() -> Result<(), Box> { let url = match Url::parse(&url_s) { Ok(u) => u, Err(e) => { - eprintln!("[error] unable to parse the URL in {}", srv_url_file.display()); + eprintln!( + "[error] unable to parse the URL in {}", + srv_url_file.display() + ); eprintln!("[error] urlparse returned error '{}'", e); eprintln!("[error] please correct the error and try again"); std::process::exit(1); @@ -284,6 +287,6 @@ async fn main2() -> Result<(), Box> { Commands::Network { command } => network_main(command, server).await, Commands::Org { command } => org_main(command, server).await, Commands::Role { command } => role_main(command, server).await, - Commands::Host { command } => host_main(command, server).await + Commands::Host { command } => host_main(command, server).await, } -} \ No newline at end of file +} diff --git a/tfcli/src/network.rs b/tfcli/src/network.rs index 6a59574..b41a75b 100644 --- a/tfcli/src/network.rs +++ b/tfcli/src/network.rs @@ -1,20 +1,20 @@ -use std::error::Error; -use std::fs; -use serde::Deserialize; -use url::Url; use crate::api::APIErrorResponse; use crate::NetworkCommands; +use serde::Deserialize; +use std::error::Error; +use std::fs; +use url::Url; pub async fn network_main(command: NetworkCommands, server: Url) -> Result<(), Box> { match command { NetworkCommands::List {} => list_networks(server).await, - NetworkCommands::Lookup {id} => get_network(id, server).await + NetworkCommands::Lookup { id } => get_network(id, server).await, } } #[derive(Deserialize)] pub struct NetworkListResp { - pub data: Vec + pub data: Vec, } #[derive(Deserialize)] @@ -29,7 +29,7 @@ pub struct Network { pub created_at: String, #[serde(rename = "lighthousesAsRelays")] pub lighthouses_as_relays: bool, - pub name: String + pub name: String, } pub async fn list_networks(server: Url) -> Result<(), Box> { @@ -43,7 +43,11 @@ pub async fn list_networks(server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join("/v1/networks")?).bearer_auth(token).send().await?; + let res = client + .get(server.join("/v1/networks")?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let resp: NetworkListResp = res.json().await?; @@ -65,7 +69,10 @@ pub async fn list_networks(server: Url) -> Result<(), Box> { } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing networks: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing networks: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -75,7 +82,7 @@ pub async fn list_networks(server: Url) -> Result<(), Box> { #[derive(Deserialize)] pub struct NetworkGetResponse { - pub data: Network + pub data: Network, } pub async fn get_network(id: String, server: Url) -> Result<(), Box> { @@ -89,7 +96,11 @@ pub async fn get_network(id: String, server: Url) -> Result<(), Box> let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join(&format!("/v1/networks/{}", id))?).bearer_auth(token).send().await?; + let res = client + .get(server.join(&format!("/v1/networks/{}", id))?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let network: Network = res.json::().await?.data; @@ -101,11 +112,13 @@ pub async fn get_network(id: String, server: Url) -> Result<(), Box> println!("Dedicated Relays: {}", !network.lighthouses_as_relays); println!(" Name: {}", network.name); println!(" Created At: {}", network.created_at); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing networks: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing networks: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } diff --git a/tfcli/src/org.rs b/tfcli/src/org.rs index 6b30dc0..e8ef6fd 100644 --- a/tfcli/src/org.rs +++ b/tfcli/src/org.rs @@ -1,10 +1,10 @@ -use std::error::Error; -use std::fs; +use crate::api::APIErrorResponse; +use crate::OrgCommands; use ipnet::Ipv4Net; use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::fs; use url::Url; -use crate::OrgCommands; -use crate::api::APIErrorResponse; pub async fn org_main(command: OrgCommands, server: Url) -> Result<(), Box> { match command { @@ -14,14 +14,14 @@ pub async fn org_main(command: OrgCommands, server: Url) -> Result<(), Box Result<(), Box> { @@ -34,7 +34,14 @@ pub async fn create_org(cidr: Ipv4Net, server: Url) -> Result<(), Box let token = format!("{} {}", session_token, auth_token); - let res = client.post(server.join("/v1/organization")?).json(&CreateOrgBody { cidr: cidr.to_string() }).bearer_auth(token).send().await?; + let res = client + .post(server.join("/v1/organization")?) + .json(&CreateOrgBody { + cidr: cidr.to_string(), + }) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let resp: OrgCreateResponse = res.json().await?; @@ -45,7 +52,10 @@ pub async fn create_org(cidr: Ipv4Net, server: Url) -> Result<(), Box } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error creating org: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error creating org: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } diff --git a/tfcli/src/role.rs b/tfcli/src/role.rs index a6ffd9c..03a7fc5 100644 --- a/tfcli/src/role.rs +++ b/tfcli/src/role.rs @@ -1,23 +1,31 @@ +use crate::api::APIErrorResponse; +use crate::RoleCommands; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::fs; -use serde::{Deserialize, Serialize}; use url::Url; -use crate::api::APIErrorResponse; -use crate::{RoleCommands}; pub async fn role_main(command: RoleCommands, server: Url) -> Result<(), Box> { match command { RoleCommands::List {} => list_roles(server).await, - RoleCommands::Lookup {id} => get_role(id, server).await, - RoleCommands::Create { name, description, rules_json } => create_role(name, description, rules_json, server).await, + RoleCommands::Lookup { id } => get_role(id, server).await, + RoleCommands::Create { + name, + description, + rules_json, + } => create_role(name, description, rules_json, server).await, RoleCommands::Delete { id } => delete_role(id, server).await, - RoleCommands::Update { id, description, rules_json } => update_role(id, description, rules_json, server).await + RoleCommands::Update { + id, + description, + rules_json, + } => update_role(id, description, rules_json, server).await, } } #[derive(Deserialize)] pub struct RoleListResp { - pub data: Vec + pub data: Vec, } #[derive(Deserialize)] @@ -30,7 +38,7 @@ pub struct Role { #[serde(rename = "createdAt")] pub created_at: String, #[serde(rename = "modifiedAt")] - pub modified_at: String + pub modified_at: String, } #[derive(Deserialize, Serialize)] pub struct RoleFirewallRule { @@ -39,12 +47,12 @@ pub struct RoleFirewallRule { #[serde(rename = "allowedRoleID")] pub allowed_role_id: Option, #[serde(rename = "portRange")] - pub port_range: Option + pub port_range: Option, } #[derive(Deserialize, Serialize)] pub struct RoleFirewallRulePortRange { pub from: u16, - pub to: u16 + pub to: u16, } pub async fn list_roles(server: Url) -> Result<(), Box> { @@ -58,7 +66,11 @@ pub async fn list_roles(server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join("/v1/roles")?).bearer_auth(token).send().await?; + let res = client + .get(server.join("/v1/roles")?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let resp: RoleListResp = res.json().await?; @@ -68,9 +80,21 @@ pub async fn list_roles(server: Url) -> Result<(), Box> { println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); - println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string())); + println!( + " Allowed Role: {}", + rule.allowed_role_id + .as_ref() + .unwrap_or(&"All roles".to_string()) + ); println!(" Protocol: {}", rule.protocol); - println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() }); + println!( + " Port Range: {}", + if let Some(pr) = rule.port_range.as_ref() { + format!("{}-{}", pr.from, pr.to) + } else { + "Any".to_string() + } + ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); @@ -82,7 +106,10 @@ pub async fn list_roles(server: Url) -> Result<(), Box> { } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing roles: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing roles: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -92,7 +119,7 @@ pub async fn list_roles(server: Url) -> Result<(), Box> { #[derive(Deserialize)] pub struct RoleGetResponse { - pub data: Role + pub data: Role, } pub async fn get_role(id: String, server: Url) -> Result<(), Box> { @@ -106,7 +133,11 @@ pub async fn get_role(id: String, server: Url) -> Result<(), Box> { let token = format!("{} {}", session_token, auth_token); - let res = client.get(server.join(&format!("/v1/roles/{}", id))?).bearer_auth(token).send().await?; + let res = client + .get(server.join(&format!("/v1/roles/{}", id))?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; @@ -115,17 +146,31 @@ pub async fn get_role(id: String, server: Url) -> Result<(), Box> { println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); - println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string())); + println!( + " Allowed Role: {}", + rule.allowed_role_id + .as_ref() + .unwrap_or(&"All roles".to_string()) + ); println!(" Protocol: {}", rule.protocol); - println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() }); + println!( + " Port Range: {}", + if let Some(pr) = rule.port_range.as_ref() { + format!("{}-{}", pr.from, pr.to) + } else { + "Any".to_string() + } + ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error listing roles: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error listing roles: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -138,10 +183,15 @@ pub struct RoleCreateBody { pub name: String, pub description: String, #[serde(rename = "firewallRules")] - pub firewall_rules: Vec + pub firewall_rules: Vec, } -pub async fn create_role(name: String, description: String, rules_json: String, server: Url) -> Result<(), Box> { +pub async fn create_role( + name: String, + description: String, + rules_json: String, + server: Url, +) -> Result<(), Box> { let client = reqwest::Client::new(); let rules: Vec = match serde_json::from_str(&rules_json) { @@ -160,11 +210,16 @@ pub async fn create_role(name: String, description: String, rules_json: String, let token = format!("{} {}", session_token, auth_token); - let res = client.post(server.join("/v1/roles")?).json(&RoleCreateBody { - name, - description, - firewall_rules: rules, - }).bearer_auth(token).send().await?; + let res = client + .post(server.join("/v1/roles")?) + .json(&RoleCreateBody { + name, + description, + firewall_rules: rules, + }) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; @@ -173,17 +228,31 @@ pub async fn create_role(name: String, description: String, rules_json: String, println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); - println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string())); + println!( + " Allowed Role: {}", + rule.allowed_role_id + .as_ref() + .unwrap_or(&"All roles".to_string()) + ); println!(" Protocol: {}", rule.protocol); - println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() }); + println!( + " Port Range: {}", + if let Some(pr) = rule.port_range.as_ref() { + format!("{}-{}", pr.from, pr.to) + } else { + "Any".to_string() + } + ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error creating role: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error creating role: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -195,10 +264,15 @@ pub async fn create_role(name: String, description: String, rules_json: String, pub struct RoleUpdateBody { pub description: String, #[serde(rename = "firewallRules")] - pub firewall_rules: Vec + pub firewall_rules: Vec, } -pub async fn update_role(id: String, description: String, rules_json: String, server: Url) -> Result<(), Box> { +pub async fn update_role( + id: String, + description: String, + rules_json: String, + server: Url, +) -> Result<(), Box> { let client = reqwest::Client::new(); let rules: Vec = match serde_json::from_str(&rules_json) { @@ -217,10 +291,15 @@ pub async fn update_role(id: String, description: String, rules_json: String, se let token = format!("{} {}", session_token, auth_token); - let res = client.put(server.join(&format!("/v1/roles/{}", id))?).json(&RoleUpdateBody { - description, - firewall_rules: rules, - }).bearer_auth(token).send().await?; + let res = client + .put(server.join(&format!("/v1/roles/{}", id))?) + .json(&RoleUpdateBody { + description, + firewall_rules: rules, + }) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; @@ -229,17 +308,31 @@ pub async fn update_role(id: String, description: String, rules_json: String, se println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); - println!(" Allowed Role: {}", rule.allowed_role_id.as_ref().unwrap_or(&"All roles".to_string())); + println!( + " Allowed Role: {}", + rule.allowed_role_id + .as_ref() + .unwrap_or(&"All roles".to_string()) + ); println!(" Protocol: {}", rule.protocol); - println!(" Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() }); + println!( + " Port Range: {}", + if let Some(pr) = rule.port_range.as_ref() { + format!("{}-{}", pr.from, pr.to) + } else { + "Any".to_string() + } + ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); - } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error updating role: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error updating role: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } @@ -258,17 +351,24 @@ pub async fn delete_role(id: String, server: Url) -> Result<(), Box> let token = format!("{} {}", session_token, auth_token); - let res = client.delete(server.join(&format!("/v1/roles/{}", id))?).bearer_auth(token).send().await?; + let res = client + .delete(server.join(&format!("/v1/roles/{}", id))?) + .bearer_auth(token) + .send() + .await?; if res.status().is_success() { println!("Role removed"); } else { let resp: APIErrorResponse = res.json().await?; - eprintln!("[error] Error removing role: {} {}", resp.errors[0].code, resp.errors[0].message); + eprintln!( + "[error] Error removing role: {} {}", + resp.errors[0].code, resp.errors[0].message + ); std::process::exit(1); } Ok(()) -} \ No newline at end of file +} diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs index 84c1ce3..03b5247 100644 --- a/tfclient/src/apiworker.rs +++ b/tfclient/src/apiworker.rs @@ -112,10 +112,7 @@ pub fn apiworker_main( cdata.creds = Some(creds); cdata.dh_privkey = Some(dh_privkey); - match fs::write( - nebula_yml(&instance), - config, - ) { + match fs::write(nebula_yml(&instance), config) { Ok(_) => (), Err(e) => { error!("unable to save nebula config: {}", e); @@ -175,10 +172,7 @@ pub fn apiworker_main( } }; - match fs::write( - nebula_yml(&instance), - config, - ) { + match fs::write(nebula_yml(&instance), config) { Ok(_) => (), Err(e) => { error!("unable to save nebula config: {}", e); diff --git a/tfclient/src/config.rs b/tfclient/src/config.rs index 5f23936..d2229d2 100644 --- a/tfclient/src/config.rs +++ b/tfclient/src/config.rs @@ -4,11 +4,11 @@ use std::error::Error; use std::fs; use std::net::{Ipv4Addr, SocketAddrV4}; +use crate::dirs::{config_dir, tfclient_toml, tfdata_toml}; use dnapi_rs::client_blocking::EnrollMeta; use dnapi_rs::credentials::Credentials; use log::{debug, info}; use serde::{Deserialize, Serialize}; -use crate::dirs::{config_dir, tfclient_toml, tfdata_toml}; pub const DEFAULT_PORT: u16 = 8157; fn default_port() -> u16 { @@ -39,10 +39,7 @@ pub fn create_config(instance: &str) -> Result<(), Box> { disable_automatic_config_updates: false, }; let config_str = toml::to_string(&config)?; - fs::write( - tfclient_toml(instance), - config_str, - )?; + fs::write(tfclient_toml(instance), config_str)?; Ok(()) } @@ -72,10 +69,7 @@ pub fn create_cdata(instance: &str) -> Result<(), Box> { meta: None, }; let config_str = toml::to_string(&config)?; - fs::write( - tfdata_toml(instance), - config_str, - )?; + fs::write(tfdata_toml(instance), config_str)?; Ok(()) } diff --git a/tfclient/src/daemon.rs b/tfclient/src/daemon.rs index d866d52..5089310 100644 --- a/tfclient/src/daemon.rs +++ b/tfclient/src/daemon.rs @@ -6,7 +6,7 @@ use std::thread; use crate::apiworker::{apiworker_main, APIWorkerMessage}; use crate::config::load_config; -use crate::nebulaworker::{NebulaWorkerMessage, nebulaworker_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, shutdown}; @@ -83,7 +83,11 @@ pub fn daemon_main(name: String, server: String) { let timer_transmitter = transmitter; let timer_thread = thread::spawn(move || { - timer_main(timer_transmitter, rx_timer, config.disable_automatic_config_updates); + timer_main( + timer_transmitter, + rx_timer, + config.disable_automatic_config_updates, + ); }); info!("Waiting for timer thread to exit..."); match timer_thread.join() { @@ -95,7 +99,6 @@ pub fn daemon_main(name: String, server: String) { } info!("Timer thread exited"); - info!("Waiting for socket thread to exit..."); match socket_thread.join() { Ok(_) => (), diff --git a/tfclient/src/dirs.rs b/tfclient/src/dirs.rs index 6495d0d..d015997 100644 --- a/tfclient/src/dirs.rs +++ b/tfclient/src/dirs.rs @@ -31,13 +31,19 @@ pub fn config_dir(instance: &str) -> PathBuf { } pub fn tfclient_toml(instance: &str) -> PathBuf { - config_base().join(format!("{}/", instance)).join("tfclient.toml") + config_base() + .join(format!("{}/", instance)) + .join("tfclient.toml") } pub fn tfdata_toml(instance: &str) -> PathBuf { - config_base().join(format!("{}/", instance)).join("tfdata.toml") + config_base() + .join(format!("{}/", instance)) + .join("tfdata.toml") } pub fn nebula_yml(instance: &str) -> PathBuf { - config_base().join(format!("{}/", instance)).join("nebula.yml") + config_base() + .join(format!("{}/", instance)) + .join("nebula.yml") } diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs index f009358..3c7f364 100644 --- a/tfclient/src/main.rs +++ b/tfclient/src/main.rs @@ -44,7 +44,6 @@ struct Cli { #[derive(Subcommand)] enum Commands { - /// Run the tfclient daemon in the foreground Run { #[clap(short, long, default_value = "tfclient")] @@ -116,4 +115,4 @@ fn main() { }; } } -} \ No newline at end of file +} diff --git a/tfclient/src/nebulaworker.rs b/tfclient/src/nebulaworker.rs index 8218db3..5e0eaad 100644 --- a/tfclient/src/nebulaworker.rs +++ b/tfclient/src/nebulaworker.rs @@ -2,13 +2,13 @@ use crate::config::{load_cdata, NebulaConfig, TFClientConfig}; use crate::daemon::ThreadMessageSender; -use crate::dirs::{nebula_yml}; +use crate::dirs::nebula_yml; +use crate::util::shutdown; use log::{debug, error, info}; +use nebula_ffi::NebulaInstance; use std::error::Error; use std::fs; use std::sync::mpsc::Receiver; -use nebula_ffi::NebulaInstance; -use crate::util::shutdown; pub enum NebulaWorkerMessage { Shutdown, @@ -23,9 +23,7 @@ fn insert_private_key(instance: &str) -> Result<(), Box> { let cdata = load_cdata(instance)?; let key = cdata.dh_privkey.ok_or("Missing private key")?; - let config_str = fs::read_to_string( - nebula_yml(instance), - )?; + let config_str = fs::read_to_string(nebula_yml(instance))?; let mut config: NebulaConfig = serde_yaml::from_str(&config_str)?; config.pki.key = Some(String::from_utf8(key)?); @@ -33,10 +31,7 @@ fn insert_private_key(instance: &str) -> Result<(), Box> { debug!("inserted private key into config: {:?}", config); let config_str = serde_yaml::to_string(&config)?; - fs::write( - nebula_yml(instance), - config_str, - )?; + fs::write(nebula_yml(instance), config_str)?; Ok(()) } @@ -74,30 +69,31 @@ pub fn nebulaworker_main( error!("not enrolled, cannot start nebula"); } else { info!("setting up nebula..."); - nebula = Some(match NebulaInstance::new(nebula_yml(&instance).as_path(), false) { - Ok(i) => { - info!("nebula setup"); - info!("starting nebula..."); - match i.start() { - Ok(_) => (), - Err(e) => { - error!("error starting Nebula: {}", e); - error!("nebula thread exiting with error"); - shutdown(&transmitter); - return; + nebula = Some( + match NebulaInstance::new(nebula_yml(&instance).as_path(), false) { + Ok(i) => { + info!("nebula setup"); + info!("starting nebula..."); + match i.start() { + Ok(_) => (), + Err(e) => { + error!("error starting Nebula: {}", e); + error!("nebula thread exiting with error"); + shutdown(&transmitter); + return; + } } + + i + } + Err(e) => { + error!("error setting up Nebula: {}", e); + error!("nebula thread exiting with error"); + shutdown(&transmitter); + return; } - - i }, - Err(e) => { - error!("error setting up Nebula: {}", e); - error!("nebula thread exiting with error"); - shutdown(&transmitter); - return; - } - - }); + ); info!("nebula process started"); } @@ -157,30 +153,31 @@ pub fn nebulaworker_main( } else { debug!("detected enrollment, starting nebula for the first time"); info!("setting up nebula..."); - nebula = Some(match NebulaInstance::new(nebula_yml(&instance).as_path(), false) { - Ok(i) => { - info!("nebula setup"); - info!("starting nebula..."); - match i.start() { - Ok(_) => (), - Err(e) => { - error!("error starting Nebula: {}", e); - error!("nebula thread exiting with error"); - shutdown(&transmitter); - return; + nebula = Some( + match NebulaInstance::new(nebula_yml(&instance).as_path(), false) { + Ok(i) => { + info!("nebula setup"); + info!("starting nebula..."); + match i.start() { + Ok(_) => (), + Err(e) => { + error!("error starting Nebula: {}", e); + error!("nebula thread exiting with error"); + shutdown(&transmitter); + return; + } } + + i + } + Err(e) => { + error!("error setting up Nebula: {}", e); + error!("nebula thread exiting with error"); + shutdown(&transmitter); + return; } - - i }, - Err(e) => { - error!("error setting up Nebula: {}", e); - error!("nebula thread exiting with error"); - shutdown(&transmitter); - return; - } - - }); + ); info!("nebula process started"); } diff --git a/tfclient/src/nebulaworker_inert.rs b/tfclient/src/nebulaworker_inert.rs index 2deb177..0d7d162 100644 --- a/tfclient/src/nebulaworker_inert.rs +++ b/tfclient/src/nebulaworker_inert.rs @@ -4,7 +4,7 @@ use crate::config::{load_cdata, NebulaConfig, TFClientConfig}; use crate::daemon::ThreadMessageSender; -use crate::dirs::{nebula_yml}; +use crate::dirs::nebula_yml; use log::{debug, error, info}; use std::error::Error; use std::fs; @@ -23,9 +23,7 @@ fn insert_private_key(instance: &str) -> Result<(), Box> { let cdata = load_cdata(instance)?; let key = cdata.dh_privkey.ok_or("Missing private key")?; - let config_str = fs::read_to_string( - nebula_yml(instance), - )?; + let config_str = fs::read_to_string(nebula_yml(instance))?; let mut config: NebulaConfig = serde_yaml::from_str(&config_str)?; config.pki.key = Some(String::from_utf8(key)?); @@ -33,25 +31,26 @@ fn insert_private_key(instance: &str) -> Result<(), Box> { debug!("inserted private key into config: {:?}", config); let config_str = serde_yaml::to_string(&config)?; - fs::write( - nebula_yml(instance), - config_str, - )?; + fs::write(nebula_yml(instance), config_str)?; Ok(()) } - -pub fn nebulaworker_main(_config: TFClientConfig, instance: String, _transmitter: ThreadMessageSender, rx: Receiver) { +pub fn nebulaworker_main( + _config: TFClientConfig, + instance: String, + _transmitter: ThreadMessageSender, + rx: Receiver, +) { loop { match rx.recv() { Ok(msg) => match msg { NebulaWorkerMessage::WakeUp => { continue; - }, + } NebulaWorkerMessage::Shutdown => { break; - }, + } NebulaWorkerMessage::ConfigUpdated => { info!("our configuration has been updated - reloading"); diff --git a/tfclient/src/timerworker.rs b/tfclient/src/timerworker.rs index d2ff244..08075dd 100644 --- a/tfclient/src/timerworker.rs +++ b/tfclient/src/timerworker.rs @@ -12,7 +12,11 @@ pub enum TimerWorkerMessage { Shutdown, } -pub fn timer_main(tx: ThreadMessageSender, rx: Receiver, disable_config_updates: bool) { +pub fn timer_main( + tx: ThreadMessageSender, + rx: Receiver, + disable_config_updates: bool, +) { let mut api_reload_timer = SystemTime::now().add(Duration::from_secs(60)); loop { diff --git a/tfclient/src/util.rs b/tfclient/src/util.rs index 9c8bffe..5277e4b 100644 --- a/tfclient/src/util.rs +++ b/tfclient/src/util.rs @@ -1,12 +1,12 @@ -use log::{error, warn}; -use sha2::Digest; -use sha2::Sha256; -use url::Url; use crate::apiworker::APIWorkerMessage; use crate::daemon::ThreadMessageSender; use crate::nebulaworker::NebulaWorkerMessage; use crate::socketworker::SocketWorkerMessage; use crate::timerworker::TimerWorkerMessage; +use log::{error, warn}; +use sha2::Digest; +use sha2::Sha256; +use url::Url; pub fn sha256(bytes: &[u8]) -> String { let mut hasher = Sha256::new(); @@ -46,15 +46,12 @@ pub fn shutdown(transmitter: &ThreadMessageSender) { Ok(_) => (), Err(e) => { error!( - "Error sending shutdown message to nebula worker thread: {}", - e - ); + "Error sending shutdown message to nebula worker thread: {}", + e + ); } } - match transmitter - .api_thread - .send(APIWorkerMessage::Shutdown) - { + match transmitter.api_thread.send(APIWorkerMessage::Shutdown) { Ok(_) => (), Err(e) => { error!("Error sending shutdown message to api worker thread: {}", e); @@ -67,21 +64,18 @@ pub fn shutdown(transmitter: &ThreadMessageSender) { Ok(_) => (), Err(e) => { error!( - "Error sending shutdown message to socket worker thread: {}", - e - ); + "Error sending shutdown message to socket worker thread: {}", + e + ); } } - match transmitter - .timer_thread - .send(TimerWorkerMessage::Shutdown) - { + match transmitter.timer_thread.send(TimerWorkerMessage::Shutdown) { Ok(_) => (), Err(e) => { error!( - "Error sending shutdown message to timer worker thread: {}", - e - ); + "Error sending shutdown message to timer worker thread: {}", + e + ); } } -} \ No newline at end of file +} diff --git a/trifid-api-old/src/codegen/mod.rs b/trifid-api-old/src/codegen/mod.rs index 809db48..a5d5970 100644 --- a/trifid-api-old/src/codegen/mod.rs +++ b/trifid-api-old/src/codegen/mod.rs @@ -15,10 +15,13 @@ use crate::crypto::{decrypt_with_nonce, get_cipher_from_config}; use crate::AppState; use ed25519_dalek::SigningKey; use ipnet::Ipv4Net; -use log::{error}; +use log::error; use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter}; use serde_yaml::{Mapping, Value}; -use trifid_api_entities::entity::{firewall_rule, host, host_config_override, host_static_address, keystore_entry, network, organization, signing_ca}; +use trifid_api_entities::entity::{ + firewall_rule, host, host_config_override, host_static_address, keystore_entry, network, + organization, signing_ca, +}; use trifid_pki::cert::{ deserialize_ed25519_private, deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails, @@ -36,7 +39,7 @@ pub struct CodegenRequiredInfo { pub lighthouse_ips: Vec, pub blocked_hosts: Vec, pub firewall_rules: Vec, - pub config_overrides: Vec<(String, String)> + pub config_overrides: Vec<(String, String)>, } pub async fn generate_config( @@ -90,14 +93,20 @@ pub async fn generate_config( let mut blocked_hosts_fingerprints = vec![]; for host in &info.blocked_hosts { - // check if the host exists - if host::Entity::find().filter(host::Column::Id.eq(host)).one(&db.conn).await?.is_some() { + if host::Entity::find() + .filter(host::Column::Id.eq(host)) + .one(&db.conn) + .await? + .is_some() + { // pull all of their certs ever and block them - let host_entries = keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(host)).all(&db.conn).await?; + let host_entries = keystore_entry::Entity::find() + .filter(keystore_entry::Column::Host.eq(host)) + .all(&db.conn) + .await?; for entry in &host_entries { - // decode the cert let cert = deserialize_nebula_certificate_from_pem(&entry.certificate)?; @@ -209,11 +218,18 @@ pub async fn generate_config( let mut current_val = &mut value; - for key_iter in &key_split[..key_split.len()-1] { - current_val = current_val.as_mapping_mut().unwrap().entry(Value::String(key_iter.to_string())).or_insert(Value::Mapping(Mapping::new())); + for key_iter in &key_split[..key_split.len() - 1] { + current_val = current_val + .as_mapping_mut() + .unwrap() + .entry(Value::String(key_iter.to_string())) + .or_insert(Value::Mapping(Mapping::new())); } - current_val.as_mapping_mut().unwrap().insert(Value::String(key_split[key_split.len()-1].to_string()), serde_yaml::from_str(kv_value)?); + current_val.as_mapping_mut().unwrap().insert( + Value::String(key_split[key_split.len() - 1].to_string()), + serde_yaml::from_str(kv_value)?, + ); } let config_str_merged = serde_yaml::to_string(&value)?; @@ -338,10 +354,10 @@ pub async fn collect_info<'a>( let best_ca = best_ca.unwrap(); // pull our host's config overrides - let config_overrides = host_config_overrides.iter().map(|u| { - (u.key.clone(), u.value.clone()) - }).collect(); - + let config_overrides = host_config_overrides + .iter() + .map(|u| (u.key.clone(), u.value.clone())) + .collect(); // pull our role's firewall rules let firewall_rules = trifid_api_entities::entity::firewall_rule::Entity::find() @@ -386,6 +402,6 @@ pub async fn collect_info<'a>( lighthouse_ips: lighthouses, blocked_hosts, firewall_rules, - config_overrides + config_overrides, }) } diff --git a/trifid-api-old/src/config.rs b/trifid-api-old/src/config.rs index ce217d8..e90ae9f 100644 --- a/trifid-api-old/src/config.rs +++ b/trifid-api-old/src/config.rs @@ -77,7 +77,7 @@ pub struct TrifidConfigServer { #[serde(default = "socketaddr_8080")] pub bind: SocketAddr, #[serde(default = "default_workers")] - pub workers: usize + pub workers: usize, } #[derive(Serialize, Deserialize, Debug)] @@ -733,4 +733,6 @@ fn is_none(o: &Option) -> bool { o.is_none() } -fn default_workers() -> usize { 32 } \ No newline at end of file +fn default_workers() -> usize { + 32 +} diff --git a/trifid-api-old/src/main.rs b/trifid-api-old/src/main.rs index e99cdc4..0a76131 100644 --- a/trifid-api-old/src/main.rs +++ b/trifid-api-old/src/main.rs @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; +use crate::tokens::random_id_no_id; +use actix_cors::Cors; use actix_request_identifier::RequestIdentifier; use actix_web::{ web::{Data, JsonConfig}, @@ -23,10 +27,6 @@ use log::{info, Level}; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use std::error::Error; use std::time::Duration; -use actix_cors::Cors; -use crate::config::CONFIG; -use crate::error::{APIError, APIErrorsResponse}; -use crate::tokens::random_id_no_id; use trifid_api_migration::{Migrator, MigratorTrait}; pub mod auth_tokens; @@ -37,10 +37,10 @@ pub mod cursor; pub mod error; //pub mod legacy_keystore; // TODO- Remove pub mod magic_link; +pub mod response; pub mod routes; pub mod timers; pub mod tokens; -pub mod response; pub struct AppState { pub conn: DatabaseConnection, @@ -84,7 +84,7 @@ async fn main() -> Result<(), Box> { errors: vec![api_error], }), ) - .into() + .into() }), ) .wrap(RequestIdentifier::with_generator(random_id_no_id)) @@ -117,10 +117,10 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::get_host_overrides) .service(routes::v1::hosts::update_host_overrides) }) - .workers(CONFIG.server.workers) - .bind(CONFIG.server.bind)? - .run() - .await?; + .workers(CONFIG.server.workers) + .bind(CONFIG.server.bind)? + .run() + .await?; Ok(()) } diff --git a/trifid-api-old/src/response.rs b/trifid-api-old/src/response.rs index 06e797e..de7e2a1 100644 --- a/trifid-api-old/src/response.rs +++ b/trifid-api-old/src/response.rs @@ -1,9 +1,9 @@ -use std::fmt::{Display, Formatter}; -use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError}; use actix_web::body::EitherBody; use actix_web::web::Json; +use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError}; use log::error; use sea_orm::DbErr; +use std::fmt::{Display, Formatter}; use crate::error::{APIError, APIErrorsResponse}; @@ -30,13 +30,15 @@ impl Responder for ErrResponse { impl From for ErrResponse { fn from(value: DbErr) -> Self { error!("database error: {}", value); - Self(APIErrorsResponse { errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error performing the database query. Please try again later.".to_string(), - path: None, - } - ] }) + Self(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error performing the database query. Please try again later." + .to_string(), + path: None, + }], + }) } } @@ -46,4 +48,4 @@ impl Display for ErrResponse { } } -impl ResponseError for ErrResponse {} \ No newline at end of file +impl ResponseError for ErrResponse {} diff --git a/trifid-api-old/src/routes/v1/dnclient.rs b/trifid-api-old/src/routes/v1/dnclient.rs index 828bdd6..2c8c6db 100644 --- a/trifid-api-old/src/routes/v1/dnclient.rs +++ b/trifid-api-old/src/routes/v1/dnclient.rs @@ -10,18 +10,20 @@ use dnapi_rs::message::{ }; use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use log::{error, warn}; +use sea_orm::{ActiveModelTrait, EntityTrait}; use std::clone::Clone; use std::time::{SystemTime, UNIX_EPOCH}; -use sea_orm::{ActiveModelTrait, EntityTrait}; -use trifid_pki::cert::{deserialize_ed25519_public, deserialize_nebula_certificate_from_pem, deserialize_x25519_public}; +use trifid_pki::cert::{ + deserialize_ed25519_public, deserialize_nebula_certificate_from_pem, deserialize_x25519_public, +}; -use trifid_api_entities::entity::{host, keystore_entry, keystore_host}; -use crate::error::APIErrorsResponse; -use sea_orm::{ColumnTrait, QueryFilter, IntoActiveModel}; -use sea_orm::ActiveValue::Set; -use crate::AppState; use crate::config::NebulaConfig; +use crate::error::APIErrorsResponse; use crate::tokens::random_id; +use crate::AppState; +use sea_orm::ActiveValue::Set; +use sea_orm::{ColumnTrait, IntoActiveModel, QueryFilter}; +use trifid_api_entities::entity::{host, keystore_entry, keystore_host}; #[post("/v1/dnclient")] pub async fn dnclient( @@ -109,7 +111,8 @@ pub async fn dnclient( log::debug!("{:x?}", keystore_data.client_signing_key); - let key = VerifyingKey::from_bytes(&keystore_data.client_signing_key.try_into().unwrap()).unwrap(); + let key = + VerifyingKey::from_bytes(&keystore_data.client_signing_key.try_into().unwrap()).unwrap(); if key.verify(req.message.as_bytes(), &signature).is_err() { // Be intentionally vague as the message is invalid. @@ -176,9 +179,7 @@ pub async fn dnclient( return HttpResponse::NotFound().json(APIErrorsResponse { errors: vec![crate::error::APIError { code: "ERR_NOT_FOUND".to_string(), - message: - "resource not found" - .to_string(), + message: "resource not found".to_string(), path: None, }], }); @@ -241,11 +242,15 @@ pub async fn dnclient( c1.pki.cert = c0.pki.cert.clone(); if c0 == c1 { // its just the cert. deserialize both and check if any details have changed - let cert0 = deserialize_nebula_certificate_from_pem(c0.pki.cert.as_bytes()).expect("generated an invalid certificate"); - let mut cert1 = deserialize_nebula_certificate_from_pem(c1.pki.cert.as_bytes()).expect("generated an invalid certificate"); + let cert0 = deserialize_nebula_certificate_from_pem(c0.pki.cert.as_bytes()) + .expect("generated an invalid certificate"); + let mut cert1 = deserialize_nebula_certificate_from_pem(c1.pki.cert.as_bytes()) + .expect("generated an invalid certificate"); cert1.details.not_before = cert0.details.not_before; cert1.details.not_after = cert0.details.not_after; - if cert0.serialize_to_pem().expect("generated invalid cert") == cert1.serialize_to_pem().expect("generated invalid cert") { + if cert0.serialize_to_pem().expect("generated invalid cert") + == cert1.serialize_to_pem().expect("generated invalid cert") + { // fake news! its fine actually config_is_different = false; } @@ -256,7 +261,10 @@ pub async fn dnclient( let config_update_avail = config_is_different || req.counter < keystore_header.counter as u32; host_am.last_out_of_date = Set(config_update_avail); - host_am.last_seen_at = Set(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64); + host_am.last_seen_at = Set(SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64); match host_am.update(&db.conn).await { Ok(_) => (), @@ -391,7 +399,7 @@ pub async fn dnclient( client_dh_key: dh_pubkey, client_signing_key: ed_pubkey, config: cfg_str.clone(), - signing_key: keystore_data.signing_key.clone() + signing_key: keystore_data.signing_key.clone(), }; match ks_entry_model.into_active_model().insert(&db.conn).await { @@ -425,7 +433,8 @@ pub async fn dnclient( } } - let signing_key = SigningKey::from_bytes(&keystore_data.signing_key.try_into().unwrap()); + let signing_key = + SigningKey::from_bytes(&keystore_data.signing_key.try_into().unwrap()); // get the signing key that the client last trusted based on its current config version let msg = DoUpdateResponse { diff --git a/trifid-api-old/src/routes/v1/hosts.rs b/trifid-api-old/src/routes/v1/hosts.rs index 16c7c07..4f1a681 100644 --- a/trifid-api-old/src/routes/v1/hosts.rs +++ b/trifid-api-old/src/routes/v1/hosts.rs @@ -76,7 +76,9 @@ use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use trifid_api_entities::entity::{host, host_config_override, host_static_address, network, organization, role}; +use trifid_api_entities::entity::{ + host, host_config_override, host_static_address, network, organization, role, +}; #[derive(Serialize, Deserialize)] pub struct ListHostsRequestOpts { @@ -417,7 +419,7 @@ pub async fn get_hosts( match (Cursor { page: cursor.page - 1, }) - .try_into() + .try_into() { Ok(r) => Some(r), Err(_) => None, @@ -429,7 +431,7 @@ pub async fn get_hosts( match (Cursor { page: cursor.page + 1, }) - .try_into() + .try_into() { Ok(r) => Some(r), Err(_) => None, @@ -577,13 +579,11 @@ pub async fn create_hosts_request( Err(e) => { error!("database error: {}", e); return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("networkID".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("networkID".to_string()), + }], }); } }; @@ -592,25 +592,21 @@ pub async fn create_hosts_request( net_id = net.id; } else { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("networkID".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("networkID".to_string()), + }], }); } if net_id != req.network_id { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("networkID".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("networkID".to_string()), + }], }); } @@ -640,7 +636,7 @@ pub async fn create_hosts_request( code: "ERR_INVALID_VALUE".to_string(), message: "lighthouse hosts must specify a static listen port".to_string(), path: Some("listenPort".to_string()), - }] + }], }); } else if req.listen_port == 0 && req.is_relay { return HttpResponse::BadRequest().json(APIErrorsResponse { @@ -648,7 +644,7 @@ pub async fn create_hosts_request( code: "ERR_INVALID_VALUE".to_string(), message: "relay hosts must specify a static listen port".to_string(), path: Some("listenPort".to_string()), - }] + }], }); } @@ -662,26 +658,24 @@ pub async fn create_hosts_request( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("role".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error validating the request. Please try again later." + .to_string(), + path: Some("role".to_string()), + }], }); } }; if roles.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("role".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("role".to_string()), + }], }); } } @@ -695,26 +689,23 @@ pub async fn create_hosts_request( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("name".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error validating the request. Please try again later." + .to_string(), + path: Some("name".to_string()), + }], }); } }; if !matching_hostname.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DUPLICATE_VALUE".to_string(), - message: "value already exists".to_string(), - path: Some("name".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DUPLICATE_VALUE".to_string(), + message: "value already exists".to_string(), + path: Some("name".to_string()), + }], }); } @@ -727,26 +718,23 @@ pub async fn create_hosts_request( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("ipAddress".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error validating the request. Please try again later." + .to_string(), + path: Some("ipAddress".to_string()), + }], }); } }; if !matching_ip.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DUPLICATE_VALUE".to_string(), - message: "value already exists".to_string(), - path: Some("ipAddress".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DUPLICATE_VALUE".to_string(), + message: "value already exists".to_string(), + path: Some("ipAddress".to_string()), + }], }); } @@ -1006,9 +994,7 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("networkID".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("networkID".to_string()), + }], }); } }; @@ -2288,13 +2264,11 @@ pub async fn create_host_and_enrollment_code( if net_id != req.network_id { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("networkID".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("networkID".to_string()), + }], }); } @@ -2324,7 +2298,7 @@ pub async fn create_host_and_enrollment_code( code: "ERR_INVALID_VALUE".to_string(), message: "lighthouse hosts must specify a static listen port".to_string(), path: Some("listenPort".to_string()), - }] + }], }); } else if req.listen_port == 0 && req.is_relay { return HttpResponse::BadRequest().json(APIErrorsResponse { @@ -2332,7 +2306,7 @@ pub async fn create_host_and_enrollment_code( code: "ERR_INVALID_VALUE".to_string(), message: "relay hosts must specify a static listen port".to_string(), path: Some("listenPort".to_string()), - }] + }], }); } @@ -2346,26 +2320,24 @@ pub async fn create_host_and_enrollment_code( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("role".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error validating the request. Please try again later." + .to_string(), + path: Some("role".to_string()), + }], }); } }; if roles.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_REFERENCE".to_string(), - message: "referenced value is invalid (perhaps it does not exist?)".to_string(), - path: Some("role".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_INVALID_REFERENCE".to_string(), + message: "referenced value is invalid (perhaps it does not exist?)".to_string(), + path: Some("role".to_string()), + }], }); } } @@ -2379,26 +2351,23 @@ pub async fn create_host_and_enrollment_code( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("name".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error validating the request. Please try again later." + .to_string(), + path: Some("name".to_string()), + }], }); } }; if !matching_hostname.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DUPLICATE_VALUE".to_string(), - message: "value already exists".to_string(), - path: Some("name".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DUPLICATE_VALUE".to_string(), + message: "value already exists".to_string(), + path: Some("name".to_string()), + }], }); } @@ -2411,26 +2380,23 @@ pub async fn create_host_and_enrollment_code( Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error validating the request. Please try again later.".to_string(), - path: Some("ipAddress".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error validating the request. Please try again later." + .to_string(), + path: Some("ipAddress".to_string()), + }], }); } }; if !matching_ip.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DUPLICATE_VALUE".to_string(), - message: "value already exists".to_string(), - path: Some("ipAddress".to_string()), - } - ], + errors: vec![APIError { + code: "ERR_DUPLICATE_VALUE".to_string(), + message: "value already exists".to_string(), + path: Some("ipAddress".to_string()), + }], }); } @@ -2588,7 +2554,11 @@ pub enum HostConfigOverrideDataOverrideValue { } #[get("/v1/hosts/{host_id}/config-overrides")] -pub async fn get_host_overrides(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_host_overrides( + id: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read let session_info = enforce_2fa(&req_info, &db.conn) .await @@ -2733,8 +2703,8 @@ pub async fn get_host_overrides(id: Path, req_info: HttpRequest, db: Dat errors: vec![APIError { code: "ERR_UNAUTHORIZED".to_string(), message: - "This resource does not exist or you do not have permission to access it." - .to_string(), + "This resource does not exist or you do not have permission to access it." + .to_string(), path: None, }], }); @@ -2752,7 +2722,11 @@ pub async fn get_host_overrides(id: Path, req_info: HttpRequest, db: Dat }); } - let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find().filter(host_config_override::Column::Host.eq(host.id)).all(&db.conn).await { + let config_overrides = match trifid_api_entities::entity::host_config_override::Entity::find() + .filter(host_config_override::Column::Host.eq(host.id)) + .all(&db.conn) + .await + { Ok(h) => h, Err(e) => { error!("Database error: {}", e); @@ -2767,25 +2741,26 @@ pub async fn get_host_overrides(id: Path, req_info: HttpRequest, db: Dat } }; - let overrides: Vec = config_overrides.iter().map(|u| { - let val; - if u.value == "true" || u.value == "false" { - val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true"); - } else if u.value.chars().all(|c| c.is_numeric()) { - val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap()); - } else { - val = HostConfigOverrideDataOverrideValue::Other(u.value.clone()); - } - HostConfigOverrideDataOverride { - key: u.key.clone(), - value: val, - } - }).collect(); + let overrides: Vec = config_overrides + .iter() + .map(|u| { + let val; + if u.value == "true" || u.value == "false" { + val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true"); + } else if u.value.chars().all(|c| c.is_numeric()) { + val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap()); + } else { + val = HostConfigOverrideDataOverrideValue::Other(u.value.clone()); + } + HostConfigOverrideDataOverride { + key: u.key.clone(), + value: val, + } + }) + .collect(); HttpResponse::Ok().json(HostConfigOverrideResponse { - data: HostConfigOverrideData { - overrides, - }, + data: HostConfigOverrideData { overrides }, }) } @@ -2795,7 +2770,12 @@ pub struct UpdateOverridesRequest { } #[put("/v1/hosts/{host_id}/config-overrides")] -pub async fn update_host_overrides(id: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn update_host_overrides( + id: Path, + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read let session_info = enforce_2fa(&req_info, &db.conn) .await @@ -2940,8 +2920,8 @@ pub async fn update_host_overrides(id: Path, req: Json, req: Json h, Err(e) => { error!("Database error: {}", e); @@ -2982,8 +2966,9 @@ pub async fn update_host_overrides(id: Path, req: Json, req: Json, req: Json h, Err(e) => { error!("Database error: {}", e); @@ -3033,24 +3023,30 @@ pub async fn update_host_overrides(id: Path, req: Json = config_overrides.iter().map(|u| { - let val; - if u.value == "true" || u.value == "false" { - val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true"); - } else if u.value.chars().all(|c| c.is_numeric()) || u.value.starts_with('-') && u.value.chars().collect::>()[1..].iter().all(|c| c.is_numeric()) { - val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap()); - } else { - val = HostConfigOverrideDataOverrideValue::Other(u.value.clone()); - } - HostConfigOverrideDataOverride { - key: u.key.clone(), - value: val, - } - }).collect(); + let overrides: Vec = config_overrides + .iter() + .map(|u| { + let val; + if u.value == "true" || u.value == "false" { + val = HostConfigOverrideDataOverrideValue::Boolean(u.value == "true"); + } else if u.value.chars().all(|c| c.is_numeric()) + || u.value.starts_with('-') + && u.value.chars().collect::>()[1..] + .iter() + .all(|c| c.is_numeric()) + { + val = HostConfigOverrideDataOverrideValue::Numeric(u.value.parse().unwrap()); + } else { + val = HostConfigOverrideDataOverrideValue::Other(u.value.clone()); + } + HostConfigOverrideDataOverride { + key: u.key.clone(), + value: val, + } + }) + .collect(); HttpResponse::Ok().json(HostConfigOverrideResponse { - data: HostConfigOverrideData { - overrides, - }, + data: HostConfigOverrideData { overrides }, }) -} \ No newline at end of file +} diff --git a/trifid-api-old/src/routes/v1/roles.rs b/trifid-api-old/src/routes/v1/roles.rs index f9da03c..bd412df 100644 --- a/trifid-api-old/src/routes/v1/roles.rs +++ b/trifid-api-old/src/routes/v1/roles.rs @@ -238,27 +238,23 @@ pub async fn create_role_request( if role.is_some() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DUPLICATE_VALUE".to_string(), - message: "value already exists".to_string(), - path: Some("name".to_string()) - } - ] - }) + errors: vec![APIError { + code: "ERR_DUPLICATE_VALUE".to_string(), + message: "value already exists".to_string(), + path: Some("name".to_string()), + }], + }); } for (id, rule) in req.firewall_rules.iter().enumerate() { if let Some(pr) = &rule.port_range { if pr.from < pr.to { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_VALUE".to_string(), - message: "from must be less than or equal to to".to_string(), - path: Some(format!("firewallRules[{}].portRange", id)) - } - ] + errors: vec![APIError { + code: "ERR_INVALID_VALUE".to_string(), + message: "from must be less than or equal to to".to_string(), + path: Some(format!("firewallRules[{}].portRange", id)), + }], }); } } @@ -1068,8 +1064,8 @@ pub async fn update_role_request( errors: vec![APIError { code: "ERR_NOT_FOUND".to_string(), message: - "This resource does not exist or you do not have permission to access it." - .to_string(), + "This resource does not exist or you do not have permission to access it." + .to_string(), path: None, }], }) diff --git a/trifid-api-old/src/routes/v2/enroll.rs b/trifid-api-old/src/routes/v2/enroll.rs index a73d0eb..21c352c 100644 --- a/trifid-api-old/src/routes/v2/enroll.rs +++ b/trifid-api-old/src/routes/v2/enroll.rs @@ -4,18 +4,20 @@ use actix_web::{post, HttpRequest, HttpResponse, Responder}; use dnapi_rs::message::{ APIError, EnrollRequest, EnrollResponse, EnrollResponseData, EnrollResponseDataOrg, }; -use ed25519_dalek::{SigningKey}; +use ed25519_dalek::SigningKey; use log::{debug, error}; use rand::rngs::OsRng; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, +}; use crate::codegen::{collect_info, generate_config}; +use crate::response::ErrResponse; use crate::AppState; use trifid_api_entities::entity::{host_enrollment_code, keystore_entry, keystore_host}; use trifid_pki::cert::{ deserialize_ed25519_public, deserialize_x25519_public, serialize_ed25519_public, }; -use crate::response::ErrResponse; use crate::timers::expired; use crate::tokens::random_id; @@ -33,7 +35,7 @@ pub async fn enroll( .filter(host_enrollment_code::Column::Id.eq(&req.code)) .one(&db.conn) .await?; - /* + /* { Ok(ci) => ci, Err(e) => { @@ -111,28 +113,32 @@ pub async fn enroll( Ok(_) => (), Err(e) => { error!("database error: {}", e); - return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { - errors: vec![APIError { - code: "ERR_DB_ERROR".to_string(), - message: - "There was an error with the database request. Please try again later." - .to_string(), - path: None, - }], - })); + return Ok( + HttpResponse::InternalServerError().json(EnrollResponse::Error { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request. Please try again later." + .to_string(), + path: None, + }], + }), + ); } } let info = match collect_info(&db, &enroll_info.host, &dh_pubkey).await { Ok(i) => i, Err(e) => { - return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { - errors: vec![APIError { - code: "ERR_CFG_GENERATION_ERROR".to_string(), - message: e.to_string(), - path: None, - }], - })); + return Ok( + HttpResponse::InternalServerError().json(EnrollResponse::Error { + errors: vec![APIError { + code: "ERR_CFG_GENERATION_ERROR".to_string(), + message: e.to_string(), + path: None, + }], + }), + ); } }; @@ -141,25 +147,34 @@ pub async fn enroll( Ok(cfg) => cfg, Err(e) => { error!("error generating configuration: {}", e); - return Ok(HttpResponse::InternalServerError().json(EnrollResponse::Error { - errors: vec![APIError { - code: "ERR_CFG_GENERATION_ERROR".to_string(), - message: "There was an error generating the host configuration.".to_string(), - path: None, - }], - })); + return Ok( + HttpResponse::InternalServerError().json(EnrollResponse::Error { + errors: vec![APIError { + code: "ERR_CFG_GENERATION_ERROR".to_string(), + message: "There was an error generating the host configuration." + .to_string(), + path: None, + }], + }), + ); } }; // delete all entries in the keystore for this host - let entries = keystore_entry::Entity::find().filter(keystore_entry::Column::Host.eq(&enroll_info.host)).all(&db.conn).await?; + let entries = keystore_entry::Entity::find() + .filter(keystore_entry::Column::Host.eq(&enroll_info.host)) + .all(&db.conn) + .await?; for entry in entries { entry.delete(&db.conn).await?; } - let host_info = keystore_host::Entity::find().filter(keystore_host::Column::Id.eq(&enroll_info.host)).one(&db.conn).await?; + let host_info = keystore_host::Entity::find() + .filter(keystore_host::Column::Id.eq(&enroll_info.host)) + .one(&db.conn) + .await?; if let Some(old_host) = host_info { old_host.delete(&db.conn).await?; @@ -183,7 +198,7 @@ pub async fn enroll( let host_header = keystore_host::Model { id: enroll_info.host.clone(), - counter: 1 + counter: 1, }; let entry = keystore_entry::Model { id: random_id("ksentry"), @@ -193,7 +208,7 @@ pub async fn enroll( client_dh_key: dh_pubkey, client_signing_key: ed_pubkey, config: cfg.clone(), - signing_key: key.to_bytes().to_vec() + signing_key: key.to_bytes().to_vec(), }; host_header.into_active_model().insert(&db.conn).await?; diff --git a/trifid-api-old/src/routes/v2/mod.rs b/trifid-api-old/src/routes/v2/mod.rs index e1535b0..cd0406b 100644 --- a/trifid-api-old/src/routes/v2/mod.rs +++ b/trifid-api-old/src/routes/v2/mod.rs @@ -1,2 +1,2 @@ pub mod enroll; -pub mod whoami; \ No newline at end of file +pub mod whoami; diff --git a/trifid-api-old/src/routes/v2/whoami.rs b/trifid-api-old/src/routes/v2/whoami.rs index c1f11ba..1da9b57 100644 --- a/trifid-api-old/src/routes/v2/whoami.rs +++ b/trifid-api-old/src/routes/v2/whoami.rs @@ -19,17 +19,17 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use actix_web::{get, HttpRequest, HttpResponse}; +use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::TIME_FORMAT; +use crate::AppState; use actix_web::web::Data; +use actix_web::{get, HttpRequest, HttpResponse}; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use serde::{Deserialize, Serialize}; use trifid_api_entities::entity::{organization, totp_authenticator}; -use crate::AppState; -use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; -use crate::error::{APIError, APIErrorsResponse}; -use crate::timers::TIME_FORMAT; #[derive(Serialize, Deserialize)] pub struct WhoamiResponse { @@ -72,13 +72,11 @@ pub async fn whoami(req_info: HttpRequest, db: Data) -> HttpResponse { Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Your authentication token is invalid.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Your authentication token is invalid.".to_string(), + path: None, + }], }); } } @@ -141,8 +139,8 @@ pub async fn whoami(req_info: HttpRequest, db: Data) -> HttpResponse { errors: vec![APIError { code: "ERR_DB_ERROR".to_string(), message: - "There was an error with the database request, please try again later." - .to_string(), + "There was an error with the database request, please try again later." + .to_string(), path: None, }], }); @@ -155,17 +153,20 @@ pub async fn whoami(req_info: HttpRequest, db: Data) -> HttpResponse { }; HttpResponse::Ok().json(WhoamiResponse { - data: WhoamiResponseData { actor_type: "user".to_string(), actor: WhoamiResponseDataActor { - id: user.id, - organization_id: org, - email: user.email, - created_at: Utc - .timestamp_opt(1, 0) - .unwrap() - .format(TIME_FORMAT) - .to_string(), - has_totp_authenticator: has_totp, - } }, + data: WhoamiResponseData { + actor_type: "user".to_string(), + actor: WhoamiResponseDataActor { + id: user.id, + organization_id: org, + email: user.email, + created_at: Utc + .timestamp_opt(1, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + has_totp_authenticator: has_totp, + }, + }, metadata: WhoamiResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api-old/trifid_api_migration/src/m20230815_225520_create_table_keystore_hosts.rs b/trifid-api-old/trifid_api_migration/src/m20230815_225520_create_table_keystore_hosts.rs index eceb6f6..e100aa1 100644 --- a/trifid-api-old/trifid_api_migration/src/m20230815_225520_create_table_keystore_hosts.rs +++ b/trifid-api-old/trifid_api_migration/src/m20230815_225520_create_table_keystore_hosts.rs @@ -6,22 +6,20 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(KeystoreHost::Table) - .col( - ColumnDef::new(KeystoreHost::Id) - .string() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(KeystoreHost::Counter) - .integer() - .not_null() - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(KeystoreHost::Table) + .col( + ColumnDef::new(KeystoreHost::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(KeystoreHost::Counter).integer().not_null()) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { diff --git a/trifid-api-old/trifid_api_migration/src/m20230815_230218_create_table_keystore_entries.rs b/trifid-api-old/trifid_api_migration/src/m20230815_230218_create_table_keystore_entries.rs index 357ef03..b142227 100644 --- a/trifid-api-old/trifid_api_migration/src/m20230815_230218_create_table_keystore_entries.rs +++ b/trifid-api-old/trifid_api_migration/src/m20230815_230218_create_table_keystore_entries.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230427_170037_create_table_hosts::Host; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,65 +7,55 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(KeystoreEntry::Table) - .col( - ColumnDef::new(KeystoreEntry::Id) - .string() - .not_null() - .primary_key() - ) - .col( - ColumnDef::new(KeystoreEntry::Host) - .string() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::Counter) - .integer() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::SigningKey) - .binary() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::ClientSigningKey) - .binary() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::ClientDHKey) - .binary() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::Config) - .string() - .not_null() - ) - .col( - ColumnDef::new(KeystoreEntry::Certificate) - .binary() - .not_null() - ) - .foreign_key( - ForeignKey::create() - .from(KeystoreEntry::Table, KeystoreEntry::Host) - .to(Host::Table, Host::Id) - ) - .index( - Index::create() - .name("idx-keystore-entry-host-counter-unique") - .table(KeystoreEntry::Table) - .col(KeystoreEntry::Host) - .col(KeystoreEntry::Counter) - .unique() - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(KeystoreEntry::Table) + .col( + ColumnDef::new(KeystoreEntry::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(KeystoreEntry::Host).string().not_null()) + .col(ColumnDef::new(KeystoreEntry::Counter).integer().not_null()) + .col( + ColumnDef::new(KeystoreEntry::SigningKey) + .binary() + .not_null(), + ) + .col( + ColumnDef::new(KeystoreEntry::ClientSigningKey) + .binary() + .not_null(), + ) + .col( + ColumnDef::new(KeystoreEntry::ClientDHKey) + .binary() + .not_null(), + ) + .col(ColumnDef::new(KeystoreEntry::Config).string().not_null()) + .col( + ColumnDef::new(KeystoreEntry::Certificate) + .binary() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .from(KeystoreEntry::Table, KeystoreEntry::Host) + .to(Host::Table, Host::Id), + ) + .index( + Index::create() + .name("idx-keystore-entry-host-counter-unique") + .table(KeystoreEntry::Table) + .col(KeystoreEntry::Host) + .col(KeystoreEntry::Counter) + .unique(), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { @@ -86,5 +76,5 @@ enum KeystoreEntry { ClientSigningKey, ClientDHKey, Config, - Certificate + Certificate, } diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 66bb4ec..8f0270c 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -42,5 +42,5 @@ pub struct ConfigEmail { pub struct ConfigTokens { pub magic_link_expiry_seconds: u64, pub session_token_expiry_seconds: u64, - pub auth_token_expiry_seconds: u64 + pub auth_token_expiry_seconds: u64, } diff --git a/trifid-api/src/error.rs b/trifid-api/src/error.rs index b255659..62160e4 100644 --- a/trifid-api/src/error.rs +++ b/trifid-api/src/error.rs @@ -2,6 +2,11 @@ use actix_web::error::{JsonPayloadError, PayloadError}; use serde::Serialize; use std::fmt::{Display, Formatter}; +#[derive(Serialize, Debug)] +pub struct APIErrorsResponse { + pub errors: Vec, +} + #[derive(Serialize, Debug)] pub struct APIErrorResponse { pub code: String, diff --git a/trifid-api/src/models.rs b/trifid-api/src/models.rs index 612db29..8045031 100644 --- a/trifid-api/src/models.rs +++ b/trifid-api/src/models.rs @@ -46,11 +46,11 @@ pub struct TotpAuthenticator { pub verified: bool, pub name: String, pub created_at: SystemTime, - pub last_seen_at: SystemTime + pub last_seen_at: SystemTime, } #[derive( -Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, + Queryable, Selectable, Insertable, Identifiable, Associations, Debug, PartialEq, Clone, )] #[diesel(belongs_to(User))] #[diesel(table_name = crate::schema::auth_tokens)] @@ -59,4 +59,4 @@ pub struct AuthToken { pub id: String, pub user_id: String, pub expires: SystemTime, -} \ No newline at end of file +} diff --git a/trifid-api/src/response.rs b/trifid-api/src/response.rs index 858024d..98e1f6e 100644 --- a/trifid-api/src/response.rs +++ b/trifid-api/src/response.rs @@ -1,4 +1,4 @@ -use crate::error::APIErrorResponse; +use crate::error::APIErrorsResponse; use actix_web::body::BoxBody; use actix_web::error::JsonPayloadError; use actix_web::http::StatusCode; @@ -8,7 +8,7 @@ use std::fmt::{Debug, Display, Formatter}; #[derive(Debug)] pub enum JsonAPIResponse { - Error(StatusCode, APIErrorResponse), + Error(StatusCode, APIErrorsResponse), Success(StatusCode, T), } @@ -87,17 +87,21 @@ macro_rules! handle_error { #[macro_export] macro_rules! make_err { ($c:expr,$m:expr,$p:expr) => { - $crate::error::APIErrorResponse { - code: $c.to_string(), - message: $m.to_string(), - path: Some($p.to_string()), + $crate::error::APIErrorsResponse { + errors: vec![$crate::error::APIErrorResponse { + code: $c.to_string(), + message: $m.to_string(), + path: Some($p.to_string()), + }], } }; ($c:expr,$m:expr) => { - $crate::error::APIErrorResponse { - code: $c.to_string(), - message: $m.to_string(), - path: None, + $crate::error::APIErrorsResponse { + errors: vec![$crate::error::APIErrorResponse { + code: $c.to_string(), + message: $m.to_string(), + path: None, + }], } }; } diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs index 947d083..d4db6ee 100644 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -1,3 +1,3 @@ pub mod magic_link; -pub mod verify_magic_link; pub mod totp; +pub mod verify_magic_link; diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs index 6b6ac10..bb73035 100644 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -1,19 +1,19 @@ -use std::time::{Duration, SystemTime}; -use actix_web::http::StatusCode; -use actix_web::{HttpRequest, post}; -use actix_web::web::{Data, Json}; -use serde::{Deserialize, Serialize}; -use crate::{AppState, auth, enforce, randid}; -use crate::response::JsonAPIResponse; -use diesel::{QueryDsl, ExpressionMethods, SelectableHelper, BelongingToDsl}; -use diesel_async::RunQueryDsl; -use totp_rs::{Algorithm, Secret, TOTP}; -use crate::schema::{auth_tokens, users, totp_authenticators}; use crate::models::{AuthToken, TotpAuthenticator, User}; +use crate::response::JsonAPIResponse; +use crate::schema::{auth_tokens, totp_authenticators, users}; +use crate::{auth, enforce, randid, AppState}; +use actix_web::http::StatusCode; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpRequest}; +use diesel::{BelongingToDsl, ExpressionMethods, QueryDsl, SelectableHelper}; +use diesel_async::RunQueryDsl; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, SystemTime}; +use totp_rs::{Algorithm, Secret, TOTP}; #[derive(Deserialize, Debug)] pub struct TotpAuthReq { - pub code: String + pub code: String, } #[derive(Serialize, Debug)] @@ -32,14 +32,27 @@ pub struct TotpAuthResp { } #[post("/v1/auth/totp")] -pub async fn totp_req(req: Json, state: Data, req_info: HttpRequest) -> JsonAPIResponse { +pub async fn totp_req( + req: Json, + state: Data, + req_info: HttpRequest, +) -> JsonAPIResponse { let mut conn = handle_error!(state.pool.get().await); let auth_info = auth!(req_info, conn); let session_token = enforce!(sess auth_info); - let user = handle_error!(users::table.find(&session_token.user_id).first::(&mut conn).await); + let user = handle_error!( + users::table + .find(&session_token.user_id) + .first::(&mut conn) + .await + ); - let authenticators: Vec = handle_error!(TotpAuthenticator::belonging_to(&user).load::(&mut conn).await); + let authenticators: Vec = handle_error!( + TotpAuthenticator::belonging_to(&user) + .load::(&mut conn) + .await + ); let mut found_valid_code = false; let mut chosen_auther = None; @@ -47,16 +60,36 @@ pub async fn totp_req(req: Json, state: Data, req_info: H for totp_auther in authenticators { if totp_auther.verified { let secret = Secret::Encoded(totp_auther.secret.clone()); - let totp_machine = handle_error!(TOTP::new(Algorithm::SHA1, 6, 1, 30, handle_error!(secret.to_bytes()), Some("Trifid".to_string()), user.email.clone())); + let totp_machine = handle_error!(TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + handle_error!(secret.to_bytes()), + Some("Trifid".to_string()), + user.email.clone() + )); let is_valid = handle_error!(totp_machine.check_current(&req.code)); - if is_valid { found_valid_code = true; chosen_auther = Some(totp_auther); break; } + if is_valid { + found_valid_code = true; + chosen_auther = Some(totp_auther); + break; + } } } if !found_valid_code { - err!(StatusCode::UNAUTHORIZED, make_err!("ERR_UNAUTHORIZED", "unauthorized")); + err!( + StatusCode::UNAUTHORIZED, + make_err!("ERR_UNAUTHORIZED", "unauthorized") + ); } - handle_error!(diesel::update(&(chosen_auther.unwrap())).set(totp_authenticators::dsl::last_seen_at.eq(SystemTime::now())).execute(&mut conn).await); + handle_error!( + diesel::update(&(chosen_auther.unwrap())) + .set(totp_authenticators::dsl::last_seen_at.eq(SystemTime::now())) + .execute(&mut conn) + .await + ); // issue auth token @@ -74,12 +107,10 @@ pub async fn totp_req(req: Json, state: Data, req_info: H .await ); - ok!( - TotpAuthResp { - data: TotpAuthRespData { - auth_token: new_token.id.clone() - }, - metadata: TotpAuthRespMeta {} - } - ) -} \ No newline at end of file + ok!(TotpAuthResp { + data: TotpAuthRespData { + auth_token: new_token.id.clone() + }, + metadata: TotpAuthRespMeta {} + }) +} diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index c4b0433..c639bd7 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -5,10 +5,10 @@ use crate::{randid, AppState}; use actix_web::http::StatusCode; use actix_web::post; use actix_web::web::{Data, Json}; +use diesel::result::OptionalExtension; use diesel::QueryDsl; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; -use diesel::result::OptionalExtension; use std::time::{Duration, SystemTime}; #[derive(Deserialize)] @@ -35,11 +35,14 @@ pub async fn verify_link_req( req: Json, state: Data, ) -> JsonAPIResponse { - - let mut conn = handle_error!(state.pool.get().await); - let token = match handle_error!(magic_links::table.find(&req.magic_link_token).first::(&mut conn).await.optional()) { + let token = match handle_error!(magic_links::table + .find(&req.magic_link_token) + .first::(&mut conn) + .await + .optional()) + { Some(t) => t, None => { err!( diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index 7dcac85..5694ad5 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -1,5 +1,8 @@ use crate::models::TotpAuthenticator; +use crate::models::User; use crate::response::JsonAPIResponse; +use crate::schema::totp_authenticators; +use crate::schema::users; use crate::{auth, enforce, randid, AppState}; use actix_web::web::{Data, Json}; use actix_web::{post, HttpRequest}; @@ -8,11 +11,8 @@ use diesel::QueryDsl; use diesel::SelectableHelper; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; -use totp_rs::{Algorithm, Secret, TOTP}; -use crate::schema::totp_authenticators; -use crate::schema::users; -use crate::models::User; use std::time::SystemTime; +use totp_rs::{Algorithm, Secret, TOTP}; #[derive(Deserialize)] pub struct TotpAuthenticatorReq {} @@ -45,11 +45,24 @@ pub async fn create_totp_auth_req( let auth_info = auth!(req_info, conn); let session_token = enforce!(sess auth_info); - let user = handle_error!(users::table.find(&session_token.user_id).first::(&mut conn).await); + let user = handle_error!( + users::table + .find(&session_token.user_id) + .first::(&mut conn) + .await + ); let secret = Secret::generate_secret(); - let totp = handle_error!(TOTP::new(Algorithm::SHA1, 6, 1, 30, handle_error!(secret.to_bytes()), Some("Trifid".to_string()), user.email.clone())); + let totp = handle_error!(TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + handle_error!(secret.to_bytes()), + Some("Trifid".to_string()), + user.email.clone() + )); let new_totp_authenticator = TotpAuthenticator { id: randid!(id "totp"), @@ -58,7 +71,7 @@ pub async fn create_totp_auth_req( verified: false, name: "".to_string(), created_at: SystemTime::now(), - last_seen_at: SystemTime::now() + last_seen_at: SystemTime::now(), }; handle_error!( @@ -77,3 +90,6 @@ pub async fn create_totp_auth_req( metadata: TotpAuthRespMeta {} }) } + +// TODO: Get, Edit, Delete +// All of these API endpoints are the same... they could probably be automated... diff --git a/trifid-api/src/routes/v1/verify_totp_authenticator.rs b/trifid-api/src/routes/v1/verify_totp_authenticator.rs index bb6c853..2bcf769 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticator.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticator.rs @@ -1,21 +1,21 @@ -use std::time::{Duration, SystemTime}; -use actix_web::http::StatusCode; -use actix_web::{HttpRequest, post}; -use actix_web::web::{Data, Json}; -use serde::{Deserialize, Serialize}; -use crate::{AppState, auth, enforce, randid}; -use crate::response::JsonAPIResponse; -use diesel::{QueryDsl, ExpressionMethods, SelectableHelper, OptionalExtension}; -use diesel_async::RunQueryDsl; -use totp_rs::{Algorithm, Secret, TOTP}; -use crate::schema::{auth_tokens, totp_authenticators, users}; use crate::models::{AuthToken, TotpAuthenticator, User}; +use crate::response::JsonAPIResponse; +use crate::schema::{auth_tokens, totp_authenticators, users}; +use crate::{auth, enforce, randid, AppState}; +use actix_web::http::StatusCode; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpRequest}; +use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper}; +use diesel_async::RunQueryDsl; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, SystemTime}; +use totp_rs::{Algorithm, Secret, TOTP}; #[derive(Deserialize, Debug)] pub struct VerifyTotpAuthReq { #[serde(rename = "totpToken")] pub totp_token: String, - pub code: String + pub code: String, } #[derive(Serialize, Debug)] @@ -34,14 +34,28 @@ pub struct TotpAuthResp { } #[post("/v1/verify-totp-authenticator")] -pub async fn verify_totp_req(req: Json, state: Data, req_info: HttpRequest) -> JsonAPIResponse { +pub async fn verify_totp_req( + req: Json, + state: Data, + req_info: HttpRequest, +) -> JsonAPIResponse { let mut conn = handle_error!(state.pool.get().await); let auth_info = auth!(req_info, conn); let session_token = enforce!(sess auth_info); - let user = handle_error!(users::table.find(&session_token.user_id).first::(&mut conn).await); + let user = handle_error!( + users::table + .find(&session_token.user_id) + .first::(&mut conn) + .await + ); - let authenticator = match handle_error!(totp_authenticators::table.find(&req.totp_token).first::(&mut conn).await.optional()) { + let authenticator = match handle_error!(totp_authenticators::table + .find(&req.totp_token) + .first::(&mut conn) + .await + .optional()) + { Some(t) => t, None => { err!( @@ -57,24 +71,43 @@ pub async fn verify_totp_req(req: Json, state: Data if authenticator.verified { err!( - StatusCode::BAD_REQUEST, - make_err!( - "ERR_INVALID_TOTP_TOKEN", - "TOTP token already verified", - "totpToken" - ) - ); + StatusCode::BAD_REQUEST, + make_err!( + "ERR_INVALID_TOTP_TOKEN", + "TOTP token already verified", + "totpToken" + ) + ); } let secret = Secret::Encoded(authenticator.secret.clone()); - let totp_machine = handle_error!(TOTP::new(Algorithm::SHA1, 6, 1, 30, handle_error!(secret.to_bytes()), Some("Trifid".to_string()), user.email.clone())); + let totp_machine = handle_error!(TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + handle_error!(secret.to_bytes()), + Some("Trifid".to_string()), + user.email.clone() + )); let is_valid = handle_error!(totp_machine.check_current(&req.code)); if !is_valid { - err!(StatusCode::UNAUTHORIZED, make_err!("ERR_UNAUTHORIZED", "unauthorized")); + err!( + StatusCode::UNAUTHORIZED, + make_err!("ERR_UNAUTHORIZED", "unauthorized") + ); } - handle_error!(diesel::update(&authenticator).set((totp_authenticators::dsl::verified.eq(true), totp_authenticators::dsl::last_seen_at.eq(SystemTime::now()))).execute(&mut conn).await); + handle_error!( + diesel::update(&authenticator) + .set(( + totp_authenticators::dsl::verified.eq(true), + totp_authenticators::dsl::last_seen_at.eq(SystemTime::now()) + )) + .execute(&mut conn) + .await + ); // issue auth token @@ -92,12 +125,10 @@ pub async fn verify_totp_req(req: Json, state: Data .await ); - ok!( - TotpAuthResp { - data: TotpAuthRespData { - auth_token: new_token.id.clone() - }, - metadata: TotpAuthRespMeta {} - } - ) -} \ No newline at end of file + ok!(TotpAuthResp { + data: TotpAuthRespData { + auth_token: new_token.id.clone() + }, + metadata: TotpAuthRespMeta {} + }) +}