267 lines
6.5 KiB
Rust
267 lines
6.5 KiB
Rust
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::{Error, Uuid};
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum VirtWebClientError {
|
|
#[error("Invalid status code from VirtWeb: {0}")]
|
|
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<Self, Self::Err> {
|
|
Ok(VMUuid(Uuid::from_str(s)?))
|
|
}
|
|
}
|
|
|
|
#[derive(serde::Serialize, Debug)]
|
|
pub struct TokenClaims {
|
|
pub sub: String,
|
|
pub iat: u64,
|
|
pub exp: u64,
|
|
pub verb: String,
|
|
pub path: String,
|
|
pub nonce: String,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, Debug)]
|
|
pub struct VMInfo {
|
|
pub uuid: VMUuid,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub architecture: String,
|
|
pub memory: usize,
|
|
pub number_vcpu: usize,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
|
pub struct VMState {
|
|
pub state: String,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
|
pub struct LoadAverage {
|
|
one: f64,
|
|
five: f64,
|
|
fifteen: f64,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
|
pub struct SystemSystemInfo {
|
|
physical_core_count: usize,
|
|
uptime: usize,
|
|
used_memory: usize,
|
|
available_memory: usize,
|
|
free_memory: usize,
|
|
load_average: LoadAverage,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
|
pub struct SystemInfo {
|
|
system: SystemSystemInfo,
|
|
}
|
|
|
|
#[derive(serde::Deserialize, Debug)]
|
|
pub struct TokenRight {
|
|
verb: String,
|
|
path: String,
|
|
}
|
|
|
|
pub type TokenRights = Vec<TokenRight>;
|
|
|
|
#[derive(serde::Deserialize)]
|
|
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::<Vec<_>>();
|
|
|
|
for r in &self.rights {
|
|
if r.verb != verb {
|
|
continue;
|
|
}
|
|
|
|
let curr_route_split = r.path.split('/').collect::<Vec<_>>();
|
|
|
|
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<VMUuid> {
|
|
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::<Vec<_>>()
|
|
}
|
|
|
|
/// Check if system info can be retrived
|
|
pub fn can_retrieve_system_info(&self) -> bool {
|
|
self.is_route_allowed("GET", "/api/server/info")
|
|
}
|
|
}
|
|
|
|
/// Perform a request on the API
|
|
async fn request<D: Display>(uri: D) -> anyhow::Result<reqwest::Response> {
|
|
let url = format!("{}{}", AppConfig::get().virtweb_base_url, uri);
|
|
log::debug!("Will query {uri}...");
|
|
|
|
let jwt = TokenClaims {
|
|
sub: AppConfig::get().virtweb_token_id.to_string(),
|
|
iat: time() - 60 * 2,
|
|
exp: time() + 60 * 3,
|
|
verb: "GET".to_string(),
|
|
path: uri.to_string(),
|
|
nonce: Uuid::new_v4().to_string(),
|
|
};
|
|
let jwt = AppConfig::get().token_private_key().sign_jwt(&jwt)?;
|
|
|
|
let res = reqwest::Client::new()
|
|
.get(url)
|
|
.header("x-token-id", &AppConfig::get().virtweb_token_id)
|
|
.header("x-token-content", jwt)
|
|
.send()
|
|
.await?;
|
|
|
|
if !res.status().is_success() {
|
|
return Err(VirtWebClientError::InvalidStatusCode(res.status().as_u16()).into());
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Perform a request on the API
|
|
async fn json_request<D: Display, E: serde::de::DeserializeOwned>(uri: D) -> anyhow::Result<E> {
|
|
Ok(request(uri).await?.json().await?)
|
|
}
|
|
|
|
/// Get current token information
|
|
pub async fn get_token_info() -> anyhow::Result<TokenInfo> {
|
|
let res: TokenInfo =
|
|
json_request(format!("/api/token/{}", AppConfig::get().virtweb_token_id)).await?;
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Get a vm information
|
|
pub async fn vm_info(id: VMUuid) -> anyhow::Result<VMInfo> {
|
|
json_request(id.route_info()).await
|
|
}
|
|
|
|
/// Get a vm information
|
|
pub async fn vm_state(id: VMUuid) -> anyhow::Result<VMState> {
|
|
json_request(id.route_state()).await
|
|
}
|
|
|
|
/// Start a vm
|
|
pub async fn vm_start(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_start()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Shutdown a vm
|
|
pub async fn vm_shutdown(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_shutdown()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Kill a vm
|
|
pub async fn vm_kill(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_kill()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Reset a vm
|
|
pub async fn vm_reset(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_reset()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Suspend a vm
|
|
pub async fn vm_suspend(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_suspend()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Resume a vm
|
|
pub async fn vm_resume(id: VMUuid) -> anyhow::Result<()> {
|
|
request(id.route_resume()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Grab a screenshot of the VM
|
|
pub async fn vm_screenshot(id: VMUuid) -> anyhow::Result<Vec<u8>> {
|
|
Ok(request(id.route_screenshot())
|
|
.await?
|
|
.bytes()
|
|
.await?
|
|
.to_vec())
|
|
}
|
|
|
|
/// Get current server information
|
|
pub async fn get_server_info() -> anyhow::Result<SystemInfo> {
|
|
json_request("/api/server/info").await
|
|
}
|