cli work
This commit is contained in:
parent
f52db518e7
commit
a0db9a7777
|
@ -8,6 +8,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/dnapi-rs/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/trifid-api/trifid_api_entities/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/trifid-api/trifid_api_migration/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tfcli/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
@ -781,6 +781,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "checked_int_cast"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
|
@ -993,6 +999,31 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.12.1",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
@ -2347,6 +2378,25 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qr2term"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2a1e77b5cd714b04247ad912b7c8fe9a1fe1d58425048249def91bcf690e4c"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"qrcode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcode"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
|
||||
dependencies = [
|
||||
"checked_int_cast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-protobuf"
|
||||
version = "0.8.1"
|
||||
|
@ -2972,6 +3022,27 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
|
@ -3242,6 +3313,20 @@ version = "0.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
|
||||
[[package]]
|
||||
name = "tfcli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap 4.2.7",
|
||||
"dirs 5.0.1",
|
||||
"ipnet",
|
||||
"qr2term",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tfclient"
|
||||
version = "0.1.8"
|
||||
|
@ -3371,9 +3456,21 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
|
|
|
@ -5,5 +5,6 @@ members = [
|
|||
"trifid-api/trifid_api_entities",
|
||||
"tfclient",
|
||||
"trifid-pki",
|
||||
"dnapi-rs"
|
||||
"dnapi-rs",
|
||||
"tfcli"
|
||||
]
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "tfcli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
url = "2.3.1"
|
||||
reqwest = { version = "0.11.17", features = ["json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
dirs = "5.0.1"
|
||||
qr2term = "0.3.1"
|
||||
ipnet = "2.7.2"
|
|
@ -0,0 +1,239 @@
|
|||
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::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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CreateAccountBody {
|
||||
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?;
|
||||
|
||||
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);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MagicLinkBody {
|
||||
#[serde(rename = "magicLinkToken")]
|
||||
pub magic_link_token: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MagicLinkSuccess {
|
||||
pub data: MagicLinkSuccessBody
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct MagicLinkSuccessBody {
|
||||
#[serde(rename = "sessionToken")]
|
||||
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?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: MagicLinkSuccess = res.json().await?;
|
||||
|
||||
let token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||
|
||||
fs::write(&token_store, resp.data.session_token)?;
|
||||
|
||||
println!("Session token saved to {}", token_store.display());
|
||||
println!("You will likely need to authorize with 2fa, run 'tfcli account mfa --code=[code]' to finish logging in, or 'tfcli account mfa-setup' to setup 2FA.");
|
||||
} else {
|
||||
let resp: APIErrorResponse = res.json().await?;
|
||||
|
||||
eprintln!("[error] Error getting session token: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiResponse {
|
||||
pub data: WhoamiResponseData,
|
||||
pub metadata: WhoamiResponseMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiResponseData {
|
||||
#[serde(rename = "actorType")]
|
||||
pub actor_type: String,
|
||||
pub actor: WhoamiResponseDataActor,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiResponseDataActor {
|
||||
pub id: String,
|
||||
#[serde(rename = "organizationID")]
|
||||
pub organization_id: Option<String>,
|
||||
pub email: String,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: String,
|
||||
#[serde(rename = "hasTOTPAuthenticator")]
|
||||
pub has_totp_authenticator: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiResponseMetadata {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateMfaResponse {
|
||||
pub data: CreateMfaResponseData
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateMfaResponseData {
|
||||
#[serde(rename = "totpToken")]
|
||||
pub totp_token: String,
|
||||
pub secret: String,
|
||||
pub url: String
|
||||
}
|
||||
|
||||
pub async fn create_mfa_authenticator(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)?;
|
||||
|
||||
// do we have mfa already?
|
||||
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?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: CreateMfaResponse = res.json().await?;
|
||||
|
||||
println!("--- TOTP SETUP INFORMATION ---");
|
||||
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!("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!("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);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MfaVerifyBody {
|
||||
#[serde(rename = "totpToken")]
|
||||
pub totp_token: String,
|
||||
pub code: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MFASuccess {
|
||||
pub data: MFASuccessBody
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct MFASuccessBody {
|
||||
#[serde(rename = "authToken")]
|
||||
pub auth_token: String
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: MFASuccess = res.json().await?;
|
||||
|
||||
let token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||
|
||||
fs::write(&token_store, resp.data.auth_token)?;
|
||||
|
||||
println!("Auth token saved to {}", token_store.display());
|
||||
println!("You are now fully logged-in. This 2fa token will expire in about 10 minutes, at which point you will need to authenticate again with 'tfcli account mfa'");
|
||||
} else {
|
||||
let resp: APIErrorResponse = res.json().await?;
|
||||
|
||||
eprintln!("[error] Error verifying MFA code: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MfaAuthBody {
|
||||
pub code: String
|
||||
}
|
||||
|
||||
pub async fn mfa_auth(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/auth/totp")?).json(&MfaAuthBody { code }).bearer_auth(session_token).send().await?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: MFASuccess = res.json().await?;
|
||||
|
||||
let token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||
|
||||
fs::write(&token_store, resp.data.auth_token)?;
|
||||
|
||||
println!("Auth token saved to {}", token_store.display());
|
||||
println!("You are now fully logged-in. This 2fa token will expire in about 10 minutes, at which point you will need to authenticate again with 'tfcli account mfa'");
|
||||
} else {
|
||||
let resp: APIErrorResponse = res.json().await?;
|
||||
|
||||
eprintln!("[error] Error verifying MFA code: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct APIErrorResponse {
|
||||
pub errors: Vec<APIError>
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct APIError {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
pub path: Option<String>
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
use std::error::Error;
|
||||
use std::fs;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ipnet::Ipv4Net;
|
||||
use url::Url;
|
||||
use crate::account::account_main;
|
||||
use crate::network::network_main;
|
||||
use crate::org::org_main;
|
||||
|
||||
pub mod account;
|
||||
pub mod api;
|
||||
pub mod network;
|
||||
pub mod org;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
#[clap(short, long, env = "TFCLI_SERVER")]
|
||||
/// The base URL of your trifid-api instance. Defaults to the value in $XDG_CONFIG_HOME/tfcli-server-url.conf or the TFCLI_SERVER environment variable.
|
||||
server: Option<Url>
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Commands {
|
||||
/// Manage your trifid account
|
||||
Account {
|
||||
#[command(subcommand)]
|
||||
command: AccountCommands
|
||||
},
|
||||
/// Manage the networks associated with your trifid account
|
||||
Network {
|
||||
#[command(subcommand)]
|
||||
command: NetworkCommands
|
||||
},
|
||||
/// Manage the organization associated with your trifid account
|
||||
Org {
|
||||
#[command(subcommand)]
|
||||
command: OrgCommands
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum AccountCommands {
|
||||
/// Create a new trifid account on the designated server
|
||||
Create {
|
||||
#[clap(short, long)]
|
||||
email: String
|
||||
},
|
||||
/// Log in to your account with a magic-link token acquired via email or the trifid-api logs.
|
||||
MagicLink {
|
||||
#[clap(short, long)]
|
||||
magic_link_token: String
|
||||
},
|
||||
/// Create a new TOTP authenticator on this account to enable authorizing with 2fa and performing all management tasks.
|
||||
MfaSetup {},
|
||||
/// Finish creating a new TOTP authenticator by inputting the code shown on your authenticator app.
|
||||
MfaSetupFinish {
|
||||
#[clap(short, long)]
|
||||
code: String,
|
||||
#[clap(short, long)]
|
||||
token: String
|
||||
},
|
||||
/// Create a new short-lived authentication token by inputting the code shown on your authenticator app.
|
||||
Mfa {
|
||||
#[clap(short, long)]
|
||||
code: String
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum NetworkCommands {
|
||||
/// List all networks associated with your trifid account.
|
||||
List {},
|
||||
/// Lookup a specific network by ID.
|
||||
Lookup {
|
||||
#[clap(short, long)]
|
||||
id: String
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum OrgCommands {
|
||||
/// Create an organization on your trifid-api server. NOTE: This command ONLY works on trifid-api servers. It will NOT work on original DN servers.
|
||||
Create {
|
||||
#[clap(short, long)]
|
||||
cidr: Ipv4Net
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
match main2().await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("There was an error during execution: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn main2() -> Result<(), Box<dyn Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// find server
|
||||
let server;
|
||||
|
||||
if let Some(srv) = args.server {
|
||||
server = srv; // Environment variable or CLI takes precedence over config file
|
||||
} else {
|
||||
let srv_url_file = dirs::config_dir().unwrap().join("tfcli-server-url.conf");
|
||||
if !srv_url_file.exists() {
|
||||
eprintln!("[error] no server URL available: '-s/--server' not provided, TFCLI_SERVER not set, and {} does not exist", srv_url_file.display());
|
||||
eprintln!("[error] please set a server url via any of these mechanisms and try again");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let url_s = fs::read_to_string(&srv_url_file)?;
|
||||
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] urlparse returned error '{}'", e);
|
||||
eprintln!("[error] please correct the error and try again");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
server = url;
|
||||
}
|
||||
|
||||
match args.command {
|
||||
Commands::Account { command } => account_main(command, server).await,
|
||||
Commands::Network { command } => network_main(command, server).await,
|
||||
Commands::Org { command } => org_main(command, server).await
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
use std::error::Error;
|
||||
use std::fs;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
use crate::api::APIErrorResponse;
|
||||
use crate::NetworkCommands;
|
||||
|
||||
pub async fn network_main(command: NetworkCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
||||
match command {
|
||||
NetworkCommands::List {} => list_networks(server).await,
|
||||
NetworkCommands::Lookup {id} => get_network(id, server).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NetworkListResp {
|
||||
pub data: Vec<Network>
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Network {
|
||||
pub id: String,
|
||||
pub cidr: String,
|
||||
#[serde(rename = "organizationID")]
|
||||
pub organization_id: String,
|
||||
#[serde(rename = "signingCAID")]
|
||||
pub signing_ca_id: String,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: String,
|
||||
#[serde(rename = "lighthousesAsRelays")]
|
||||
pub lighthouses_as_relays: bool,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
pub async fn list_networks(server: Url) -> Result<(), Box<dyn Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// load session token
|
||||
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||
|
||||
let token = format!("{} {}", session_token, auth_token);
|
||||
|
||||
let res = client.get(server.join("/v1/networks")?).bearer_auth(token).send().await?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: NetworkListResp = res.json().await?;
|
||||
|
||||
for network in &resp.data {
|
||||
println!(" Network: {}", network.id);
|
||||
println!(" CIDR: {}", network.cidr);
|
||||
println!(" Organization: {}", network.organization_id);
|
||||
println!(" Signing CA: {}", network.signing_ca_id);
|
||||
println!("Dedicated Relays: {}", !network.lighthouses_as_relays);
|
||||
println!(" Name: {}", network.name);
|
||||
println!(" Created At: {}", network.created_at);
|
||||
println!();
|
||||
}
|
||||
|
||||
if resp.data.is_empty() {
|
||||
println!("No networks found");
|
||||
}
|
||||
} else {
|
||||
let resp: APIErrorResponse = res.json().await?;
|
||||
|
||||
eprintln!("[error] Error listing networks: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NetworkGetResponse {
|
||||
pub data: Network
|
||||
}
|
||||
|
||||
pub async fn get_network(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// load session token
|
||||
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||
|
||||
let token = format!("{} {}", session_token, auth_token);
|
||||
|
||||
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::<NetworkGetResponse>().await?.data;
|
||||
|
||||
println!(" Network: {}", network.id);
|
||||
println!(" CIDR: {}", network.cidr);
|
||||
println!(" Organization: {}", network.organization_id);
|
||||
println!(" Signing CA: {}", network.signing_ca_id);
|
||||
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);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use std::error::Error;
|
||||
use std::fs;
|
||||
use ipnet::Ipv4Net;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use crate::OrgCommands;
|
||||
use crate::api::APIErrorResponse;
|
||||
|
||||
pub async fn org_main(command: OrgCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
||||
match command {
|
||||
OrgCommands::Create { cidr } => create_org(cidr, server).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CreateOrgBody {
|
||||
pub cidr: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct OrgCreateResponse {
|
||||
pub organization: String,
|
||||
pub ca: String,
|
||||
pub network: String
|
||||
}
|
||||
|
||||
pub async fn create_org(cidr: Ipv4Net, server: Url) -> Result<(), Box<dyn Error>> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
||||
let session_token = fs::read_to_string(&sess_token_store)?;
|
||||
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
||||
let auth_token = fs::read_to_string(&auth_token_store)?;
|
||||
|
||||
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?;
|
||||
|
||||
if res.status().is_success() {
|
||||
let resp: OrgCreateResponse = res.json().await?;
|
||||
println!("Created organization successfully.");
|
||||
println!("Organization: {}", resp.organization);
|
||||
println!(" Signing CA: {}", resp.ca);
|
||||
println!(" Network: {}", resp.network);
|
||||
} else {
|
||||
let resp: APIErrorResponse = res.json().await?;
|
||||
|
||||
eprintln!("[error] Error creating org: {} {}", resp.errors[0].code, resp.errors[0].message);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue