From 50600e4e565c01e736a3b187a1c0aa4ef8721024 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 3 May 2024 19:19:37 +0200 Subject: [PATCH] Can get VM caps --- remote_backend/Cargo.lock | 25 ++++ remote_backend/Cargo.toml | 5 +- remote_backend/src/controllers/mod.rs | 1 + .../src/controllers/vm_controller.rs | 48 ++++++++ remote_backend/src/main.rs | 3 +- remote_backend/src/virtweb_client.rs | 112 +++++++++++++++++- 6 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 remote_backend/src/controllers/vm_controller.rs diff --git a/remote_backend/Cargo.lock b/remote_backend/Cargo.lock index 379cb9d..166b5ce 100644 --- a/remote_backend/Cargo.lock +++ b/remote_backend/Cargo.lock @@ -1250,6 +1250,29 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.60", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1716,6 +1739,7 @@ dependencies = [ "clap", "env_logger", "futures-util", + "lazy-regex", "lazy_static", "light-openid", "log", @@ -2344,6 +2368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", + "serde", ] [[package]] diff --git a/remote_backend/Cargo.toml b/remote_backend/Cargo.toml index 017eb44..09e4c47 100644 --- a/remote_backend/Cargo.toml +++ b/remote_backend/Cargo.toml @@ -21,5 +21,6 @@ lazy_static = "1.4.0" anyhow = "1.0.82" reqwest = { version = "0.12.4", features = ["json"] } thiserror = "1.0.59" -uuid = { version = "1.8.0", features = ["v4"] } -futures-util = "0.3.30" \ No newline at end of file +uuid = { version = "1.8.0", features = ["v4", "serde"] } +futures-util = "0.3.30" +lazy-regex = "3.1.0" \ No newline at end of file diff --git a/remote_backend/src/controllers/mod.rs b/remote_backend/src/controllers/mod.rs index 128ba83..bbf56d0 100644 --- a/remote_backend/src/controllers/mod.rs +++ b/remote_backend/src/controllers/mod.rs @@ -7,6 +7,7 @@ use std::io::ErrorKind; pub mod auth_controller; pub mod server_controller; +pub mod vm_controller; /// Custom error to ease controller writing #[derive(Debug)] diff --git a/remote_backend/src/controllers/vm_controller.rs b/remote_backend/src/controllers/vm_controller.rs new file mode 100644 index 0000000..e821a29 --- /dev/null +++ b/remote_backend/src/controllers/vm_controller.rs @@ -0,0 +1,48 @@ +//! # Virtual machines routes controller + +use crate::controllers::HttpResult; +use crate::virtweb_client; +use crate::virtweb_client::VMUuid; +use actix_web::HttpResponse; + +#[derive(Debug, serde::Serialize)] +pub struct VMInfoAndCaps { + uiid: VMUuid, + name: String, + description: Option, + can_get_state: bool, + can_start: bool, + can_shutdown: bool, + can_kill: bool, + can_reset: bool, + can_suspend: bool, + can_resume: bool, + can_screenshot: bool, +} + +/// Get the list of VMs that can be controlled by VirtWeb remote +pub async fn list() -> HttpResult { + let rights = virtweb_client::get_token_info().await?; + + let mut res = vec![]; + + for v in rights.list_vm() { + let vm_info = virtweb_client::get_vm_info(v).await?; + + res.push(VMInfoAndCaps { + uiid: vm_info.uuid, + name: vm_info.name, + description: vm_info.description.clone(), + can_get_state: rights.is_route_allowed("GET", &v.route_state()), + can_start: rights.is_route_allowed("GET", &v.route_start()), + can_shutdown: rights.is_route_allowed("GET", &v.route_shutdown()), + can_kill: rights.is_route_allowed("GET", &v.route_kill()), + can_reset: rights.is_route_allowed("GET", &v.route_reset()), + can_suspend: rights.is_route_allowed("GET", &v.route_suspend()), + can_resume: rights.is_route_allowed("GET", &v.route_resume()), + can_screenshot: rights.is_route_allowed("GET", &v.route_screenshot()), + }) + } + + Ok(HttpResponse::Ok().json(res)) +} diff --git a/remote_backend/src/main.rs b/remote_backend/src/main.rs index 92453cd..6b35070 100644 --- a/remote_backend/src/main.rs +++ b/remote_backend/src/main.rs @@ -11,7 +11,7 @@ use actix_web::{web, App, HttpServer}; use light_openid::basic_state_manager::BasicStateManager; use remote_backend::app_config::AppConfig; use remote_backend::constants; -use remote_backend::controllers::{auth_controller, server_controller}; +use remote_backend::controllers::{auth_controller, server_controller, vm_controller}; use remote_backend::middlewares::auth_middleware::AuthChecker; use std::time::Duration; @@ -79,6 +79,7 @@ async fn main() -> std::io::Result<()> { "/api/auth/sign_out", web::get().to(auth_controller::sign_out), ) + .route("/api/vm/list", web::get().to(vm_controller::list)) }) .bind(&AppConfig::get().listen_address)? .run() diff --git a/remote_backend/src/virtweb_client.rs b/remote_backend/src/virtweb_client.rs index c8378e1..26d5190 100644 --- a/remote_backend/src/virtweb_client.rs +++ b/remote_backend/src/virtweb_client.rs @@ -1,8 +1,10 @@ use crate::app_config::AppConfig; use crate::utils::time; +use lazy_regex::regex; use std::fmt::Display; +use std::str::FromStr; use thiserror::Error; -use uuid::Uuid; +use uuid::{Error, Uuid}; #[derive(Error, Debug)] pub enum VirtWebClientError { @@ -10,6 +12,53 @@ pub enum VirtWebClientError { InvalidStatusCode(u16), } +#[derive(Eq, PartialEq, Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] +pub struct VMUuid(Uuid); + +impl VMUuid { + pub fn route_info(&self) -> String { + format!("/api/vm/{}", self.0) + } + + pub fn route_state(&self) -> String { + format!("/api/vm/{}/state", self.0) + } + + pub fn route_start(&self) -> String { + format!("/api/vm/{}/start", self.0) + } + + pub fn route_shutdown(&self) -> String { + format!("/api/vm/{}/shutdown", self.0) + } + pub fn route_kill(&self) -> String { + format!("/api/vm/{}/kill", self.0) + } + + pub fn route_reset(&self) -> String { + format!("/api/vm/{}/reset", self.0) + } + pub fn route_suspend(&self) -> String { + format!("/api/vm/{}/suspend", self.0) + } + + pub fn route_resume(&self) -> String { + format!("/api/vm/{}/resume", self.0) + } + + pub fn route_screenshot(&self) -> String { + format!("/api/vm/{}/screenshot", self.0) + } +} + +impl FromStr for VMUuid { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(VMUuid(Uuid::from_str(s)?)) + } +} + #[derive(serde::Serialize, Debug)] pub struct TokenClaims { pub sub: String, @@ -20,6 +69,13 @@ pub struct TokenClaims { pub nonce: String, } +#[derive(serde::Deserialize, Debug)] +pub struct VMInfo { + pub uuid: VMUuid, + pub name: String, + pub description: Option, +} + #[derive(serde::Deserialize, Debug)] pub struct TokenRight { verb: String, @@ -29,10 +85,49 @@ pub struct TokenRight { pub type TokenRights = Vec; #[derive(serde::Deserialize)] -struct TokenInfo { +pub struct TokenInfo { rights: TokenRights, } +impl TokenInfo { + /// Check whether a route is allowed or not + pub fn is_route_allowed(&self, verb: &str, route: &str) -> bool { + let search_route_split = route.split('/').collect::>(); + + for r in &self.rights { + if r.verb != verb { + continue; + } + + let curr_route_split = r.path.split('/').collect::>(); + + if search_route_split.len() != curr_route_split.len() { + continue; + } + + if curr_route_split + .iter() + .zip(search_route_split.iter()) + .all(|(curr, search)| curr == &"*" || curr == search) + { + return true; + } + } + + false + } + + /// List the virtual machines with access + pub fn list_vm(&self) -> Vec { + self.rights + .iter() + .filter(|r| r.verb == "GET") + .filter(|r| regex!("^/api/vm/[^/]+$").is_match(&r.path)) + .map(|r| VMUuid::from_str(r.path.rsplit_once('/').unwrap().1).unwrap()) + .collect::>() + } +} + /// Perform a request on the API async fn request(uri: D) -> anyhow::Result { let url = format!("{}{}", AppConfig::get().virtweb_base_url, uri); @@ -62,10 +157,17 @@ async fn request(uri: D) -> anyhow:: Ok(res.json().await?) } -/// Get current token rights -pub async fn get_token_rights() -> anyhow::Result { +/// Get current token information +pub async fn get_token_info() -> anyhow::Result { let res: TokenInfo = request(format!("/api/token/{}", AppConfig::get().virtweb_token_id)).await?; - Ok(res.rights) + Ok(res) +} + +/// Get a vm information +pub async fn get_vm_info(id: VMUuid) -> anyhow::Result { + let res: VMInfo = request(format!("/api/vm/{}", id.0)).await?; + + Ok(res) }