0.3 error fixes, refmt
/ build (push) Successful in 49s Details
/ build_x64 (push) Successful in 2m6s Details
/ build_arm64 (push) Successful in 2m40s Details
/ build_win64 (push) Successful in 2m36s Details

This commit is contained in:
core 2023-11-23 15:23:52 -05:00
parent 2a5a2bb910
commit 646340b637
Signed by: core
GPG Key ID: FDBF740DADDCEECF
41 changed files with 1441 additions and 878 deletions

View File

@ -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,

View File

@ -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 |

View File

@ -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<std::path::PathBuf, Box<dyn std::error::Error>> {
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);
@ -20,7 +20,6 @@ fn get_cargo_target_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Erro
}
fn main() {
// Find compiler:
// 1. GOC
// 2. /usr/local/go/bin/go
@ -49,7 +48,14 @@ fn main() {
let out = out_path.join(out_file);
let mut command = process::Command::new(compiler);
command.args(["build", "-buildmode", link_type().as_str(), "-o", out.display().to_string().as_str(), "main.go"]);
command.args([
"build",
"-buildmode",
link_type().as_str(),
"-o",
out.display().to_string().as_str(),
"main.go",
]);
command.env("CGO_ENABLED", "1");
command.env("CC", c_compiler.path());
command.env("GOARCH", goarch());
@ -68,7 +74,10 @@ fn main() {
copy_if_windows();
print_link();
println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap());
println!(
"cargo:rustc-link-search=native={}",
env::var("OUT_DIR").unwrap()
);
//let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
@ -85,7 +94,6 @@ fn main() {
.generate()
.expect("Error generating CFFI bindings");
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
@ -125,8 +133,9 @@ fn goarch() -> 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()
}
}
}

View File

@ -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<Self, Box<dyn Error>> {
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::<c_char>(), config_test_u8);
res = generated::NebulaSetup(
config_path_bytes.as_mut_ptr().cast::<c_char>(),
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(),
}
}
}
}
}

View File

@ -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<dyn Error>> {
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<dyn Error>> {
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<dyn Er
#[derive(Serialize)]
pub struct LoginAccountBody {
pub email: String
pub email: String,
}
pub async fn login_account(email: String, server: Url) -> Result<(), Box<dyn Error>> {
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<dyn Err
Ok(())
}
#[derive(Serialize)]
pub struct MagicLinkBody {
#[serde(rename = "magicLinkToken")]
pub magic_link_token: String
pub magic_link_token: String,
}
#[derive(Deserialize)]
pub struct MagicLinkSuccess {
pub data: MagicLinkSuccessBody
pub data: MagicLinkSuccessBody,
}
#[derive(Deserialize)]
pub struct MagicLinkSuccessBody {
#[serde(rename = "sessionToken")]
pub session_token: String
pub session_token: String,
}
pub async fn auth_magic_link(magic_token: String, server: Url) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
@ -153,14 +177,25 @@ pub async fn create_mfa_authenticator(server: Url) -> Result<(), Box<dyn Error>>
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<dyn Error>>
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<dyn Error>>
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<dyn Error>> {
pub async fn finish_mfa_authenticator(
token: String,
code: String,
server: Url,
) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
@ -242,7 +301,12 @@ pub async fn mfa_auth(code: String, server: Url) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
} 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);
}

View File

@ -2,11 +2,11 @@ use serde::Deserialize;
#[derive(Deserialize)]
pub struct APIErrorResponse {
pub errors: Vec<APIError>
pub errors: Vec<APIError>,
}
#[derive(Deserialize)]
pub struct APIError {
pub code: String,
pub message: String,
pub path: Option<String>
}
pub path: Option<String>,
}

View File

@ -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<dyn Error>> {
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<Host>
pub data: Vec<Host>,
}
#[derive(Serialize, Deserialize)]
pub struct HostMetadata {
#[serde(rename = "lastSeenAt")]
@ -77,7 +111,11 @@ pub async fn list_hosts(server: Url) -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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::<Vec<_>>().join(", "));
println!(
" Static Addresses: {}",
host.static_addresses
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.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<dyn Error>> {
} 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<dyn Error>> {
Ok(())
}
#[derive(Serialize, Deserialize)]
pub struct HostCreateBody {
pub name: String,
@ -134,11 +193,9 @@ pub struct HostCreateBody {
pub static_addresses: Vec<SocketAddrV4>,
}
#[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<u16>, lighthouse: bool, relay: bool, static_address: Option<SocketAddrV4>, server: Url) -> Result<(), Box<dyn Error>> {
pub async fn create_host(
name: String,
network_id: String,
role_id: String,
ip_address: Ipv4Addr,
listen_port: Option<u16>,
lighthouse: bool,
relay: bool,
static_address: Option<SocketAddrV4>,
server: Url,
) -> Result<(), Box<dyn Error>> {
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::<HostGetResponse>().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::<Vec<_>>().join(", "));
println!(
" Static Addresses: {}",
host.static_addresses
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.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<dyn Error>> {
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::<HostGetResponse>().await?.data;
@ -234,21 +331,42 @@ pub async fn get_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
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::<Vec<_>>().join(", "));
println!(
" Static Addresses: {}",
host.static_addresses
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.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<dyn Error>>
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<SocketAddrV4>,
pub name: Option<String>,
pub ip: Option<Ipv4Addr>,
pub role: Option<String>
pub role: Option<String>,
}
pub async fn update_host(id: String, listen_port: Option<u16>, static_address: Option<SocketAddrV4>, name: Option<String>, ip: Option<Ipv4Addr>, role: Option<String>, server: Url) -> Result<(), Box<dyn Error>> {
pub async fn update_host(
id: String,
listen_port: Option<u16>,
static_address: Option<SocketAddrV4>,
name: Option<String>,
ip: Option<Ipv4Addr>,
role: Option<String>,
server: Url,
) -> Result<(), Box<dyn Error>> {
let client = reqwest::Client::new();
// load session token
@ -304,13 +437,18 @@ pub async fn update_host(id: String, listen_port: Option<u16>, 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::<HostGetResponse>().await?.data;
@ -320,21 +458,42 @@ pub async fn update_host(id: String, listen_port: Option<u16>, 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::<Vec<_>>().join(", "));
println!(
" Static Addresses: {}",
host.static_addresses
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.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<u16>, 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<dyn Error>>
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<dyn Error>> {
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<dyn Error>> {
#[derive(Serialize, Deserialize)]
pub struct HostConfigOverrideResponse {
pub data: HostConfigOverrideData
pub data: HostConfigOverrideData,
}
#[derive(Serialize, Deserialize)]
pub struct HostConfigOverrideData {
pub overrides: Vec<HostConfigOverrideDataOverride>
pub overrides: Vec<HostConfigOverrideDataOverride>,
}
#[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<dyn Error>> {
@ -456,18 +633,25 @@ pub async fn list_overrides(id: String, server: Url) -> Result<(), Box<dyn Error
let token = format!("{} {}", session_token, auth_token);
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token).send().await?;
let res = client
.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?)
.bearer_auth(token)
.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() {
@ -476,7 +660,10 @@ pub async fn list_overrides(id: String, server: Url) -> Result<(), Box<dyn Error
} else {
let resp: APIErrorResponse = res.json().await?;
eprintln!("[error] Error looking up config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
eprintln!(
"[error] Error looking up config overrides: {} {}",
resp.errors[0].code, resp.errors[0].message
);
std::process::exit(1);
}
@ -486,14 +673,24 @@ pub async fn list_overrides(id: String, server: Url) -> Result<(), Box<dyn Error
#[derive(Serialize, Deserialize)]
pub struct SetOverrideRequest {
pub overrides: Vec<HostConfigOverrideDataOverride>
pub overrides: Vec<HostConfigOverrideDataOverride>,
}
pub async fn set_override(id: String, key: String, boolean: Option<bool>, numeric: Option<i64>, other: Option<String>, server: Url) -> Result<(), Box<dyn Error>> {
pub async fn set_override(
id: String,
key: String,
boolean: Option<bool>,
numeric: Option<i64>,
other: Option<String>,
server: Url,
) -> Result<(), Box<dyn Error>> {
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<bool>, 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<bool>, 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<bool>, 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
);