From 4dd84e61e04f81771dea7cc66e8ec956e0506052 Mon Sep 17 00:00:00 2001 From: core Date: Thu, 15 Jun 2023 22:00:09 -0400 Subject: [PATCH] role cli --- Cargo.lock | 1 + tfcli/Cargo.toml | 3 +- tfcli/src/main.rs | 46 +++++++- tfcli/src/role.rs | 274 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 tfcli/src/role.rs diff --git a/Cargo.lock b/Cargo.lock index c01a55a..13d30af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3323,6 +3323,7 @@ dependencies = [ "qr2term", "reqwest", "serde", + "serde_json", "tokio", "url", ] diff --git a/tfcli/Cargo.toml b/tfcli/Cargo.toml index a6ef972..673a357 100644 --- a/tfcli/Cargo.toml +++ b/tfcli/Cargo.toml @@ -13,4 +13,5 @@ serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } dirs = "5.0.1" qr2term = "0.3.1" -ipnet = "2.7.2" \ No newline at end of file +ipnet = "2.7.2" +serde_json = "1.0.96" \ No newline at end of file diff --git a/tfcli/src/main.rs b/tfcli/src/main.rs index c1d2c86..2e25e0d 100644 --- a/tfcli/src/main.rs +++ b/tfcli/src/main.rs @@ -6,11 +6,13 @@ use url::Url; use crate::account::account_main; use crate::network::network_main; use crate::org::org_main; +use crate::role::role_main; pub mod account; pub mod api; pub mod network; pub mod org; +pub mod role; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -39,6 +41,11 @@ pub enum Commands { Org { #[command(subcommand)] command: OrgCommands + }, + /// Manage the roles associated with your trifid organization + Role { + #[command(subcommand)] + command: RoleCommands } } @@ -90,6 +97,42 @@ pub enum OrgCommands { } } +#[derive(Subcommand, Debug)] +pub enum RoleCommands { + /// Create a role on your organization + Create { + #[clap(short, long)] + name: String, + #[clap(short, long)] + description: String, + /// A JSON string containing the firewall rules to add to this host + #[clap(short, long)] + 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 + }, + /// Delete a specific role by it's ID + Delete { + #[clap(short, long)] + 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 { + #[clap(short, long)] + id: String, + #[clap(short, long)] + description: String, + /// A JSON string containing the firewall rules to add to this host + #[clap(short, long)] + rules_json: String + } +} + #[tokio::main] async fn main() { match main2().await { @@ -132,6 +175,7 @@ async fn main2() -> Result<(), Box> { 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 + Commands::Org { command } => org_main(command, server).await, + Commands::Role { command } => role_main(command, server).await } } \ No newline at end of file diff --git a/tfcli/src/role.rs b/tfcli/src/role.rs new file mode 100644 index 0000000..5bbde84 --- /dev/null +++ b/tfcli/src/role.rs @@ -0,0 +1,274 @@ +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::Delete { id } => delete_role(id, server).await, + RoleCommands::Update { id, description, rules_json } => update_role(id, description, rules_json, server).await + } +} + +#[derive(Deserialize)] +pub struct RoleListResp { + pub data: Vec +} + +#[derive(Deserialize)] +pub struct Role { + pub id: String, + pub name: String, + pub description: String, + #[serde(rename = "firewallRules")] + pub firewall_rules: Vec, + #[serde(rename = "createdAt")] + pub created_at: String, + #[serde(rename = "modifiedAt")] + pub modified_at: String +} +#[derive(Deserialize, Serialize)] +pub struct RoleFirewallRule { + pub protocol: String, + pub description: String, + #[serde(rename = "allowedRoleID")] + pub allowed_role_id: Option, + #[serde(rename = "portRange")] + pub port_range: Option +} +#[derive(Deserialize, Serialize)] +pub struct RoleFirewallRulePortRange { + pub from: u16, + pub to: u16 +} + +pub async fn list_roles(server: Url) -> Result<(), Box> { + 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/roles")?).bearer_auth(token).send().await?; + + if res.status().is_success() { + let resp: RoleListResp = res.json().await?; + + for role in &resp.data { + println!(" Role: {} ({})", role.name, role.id); + 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!(" 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!(" Created: {}", role.created_at); + println!(" Updated: {}", role.modified_at); + } + + if resp.data.is_empty() { + println!("No roles found"); + } + } else { + let resp: APIErrorResponse = res.json().await?; + + eprintln!("[error] Error listing roles: {} {}", resp.errors[0].code, resp.errors[0].message); + + std::process::exit(1); + } + + Ok(()) +} + +#[derive(Deserialize)] +pub struct RoleGetResponse { + pub data: Role +} + +pub async fn get_role(id: String, server: Url) -> Result<(), Box> { + 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/roles/{}", id))?).bearer_auth(token).send().await?; + + if res.status().is_success() { + let role: Role = res.json::().await?.data; + + println!(" Role: {} ({})", role.name, role.id); + 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!(" 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!(" 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); + + std::process::exit(1); + } + + Ok(()) +} + +#[derive(Serialize)] +pub struct RoleCreateBody { + pub name: String, + pub description: String, + #[serde(rename = "firewallRules")] + pub firewall_rules: Vec +} + +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) { + Ok(r) => r, + Err(e) => { + eprintln!("[error] error parsing rulesJson: {}", e); + std::process::exit(1); + } + }; + + // 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.post(server.join(&format!("/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; + + println!(" Role: {} ({})", role.name, role.id); + 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!(" 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!(" 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); + + std::process::exit(1); + } + + Ok(()) +} + +#[derive(Serialize)] +pub struct RoleUpdateBody { + pub description: String, + #[serde(rename = "firewallRules")] + pub firewall_rules: Vec +} + +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) { + Ok(r) => r, + Err(e) => { + eprintln!("[error] error parsing rulesJson: {}", e); + std::process::exit(1); + } + }; + + // 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.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; + + println!(" Role: {} ({})", role.name, role.id); + 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!(" 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!(" 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); + + std::process::exit(1); + } + + Ok(()) +} + +pub async fn delete_role(id: String, server: Url) -> Result<(), Box> { + 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.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); + + std::process::exit(1); + } + + Ok(()) +} \ No newline at end of file