role cli
This commit is contained in:
parent
a0db9a7777
commit
4dd84e61e0
|
@ -3323,6 +3323,7 @@ dependencies = [
|
||||||
"qr2term",
|
"qr2term",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,4 +13,5 @@ serde = { version = "1", features = ["derive"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
qr2term = "0.3.1"
|
qr2term = "0.3.1"
|
||||||
ipnet = "2.7.2"
|
ipnet = "2.7.2"
|
||||||
|
serde_json = "1.0.96"
|
|
@ -6,11 +6,13 @@ use url::Url;
|
||||||
use crate::account::account_main;
|
use crate::account::account_main;
|
||||||
use crate::network::network_main;
|
use crate::network::network_main;
|
||||||
use crate::org::org_main;
|
use crate::org::org_main;
|
||||||
|
use crate::role::role_main;
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
|
pub mod role;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
@ -39,6 +41,11 @@ pub enum Commands {
|
||||||
Org {
|
Org {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: OrgCommands
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
match main2().await {
|
match main2().await {
|
||||||
|
@ -132,6 +175,7 @@ async fn main2() -> Result<(), Box<dyn Error>> {
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Account { command } => account_main(command, server).await,
|
Commands::Account { command } => account_main(command, server).await,
|
||||||
Commands::Network { command } => network_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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<dyn Error>> {
|
||||||
|
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<Role>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Role {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
#[serde(rename = "firewallRules")]
|
||||||
|
pub firewall_rules: Vec<RoleFirewallRule>,
|
||||||
|
#[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<String>,
|
||||||
|
#[serde(rename = "portRange")]
|
||||||
|
pub port_range: Option<RoleFirewallRulePortRange>
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct RoleFirewallRulePortRange {
|
||||||
|
pub from: u16,
|
||||||
|
pub to: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_roles(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/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<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/roles/{}", id))?).bearer_auth(token).send().await?;
|
||||||
|
|
||||||
|
if res.status().is_success() {
|
||||||
|
let role: Role = res.json::<RoleGetResponse>().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<RoleFirewallRule>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_role(name: String, description: String, rules_json: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let rules: Vec<RoleFirewallRule> = 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::<RoleGetResponse>().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<RoleFirewallRule>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_role(id: String, description: String, rules_json: String, server: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let rules: Vec<RoleFirewallRule> = 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::<RoleGetResponse>().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<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.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(())
|
||||||
|
}
|
Loading…
Reference in New Issue