Issue tokens to initialize VNC connections
This commit is contained in:
parent
3042bbdac6
commit
4f75cd918d
1
virtweb_backend/Cargo.lock
generated
1
virtweb_backend/Cargo.lock
generated
@ -2523,6 +2523,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"light-openid",
|
||||
"log",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
|
@ -32,4 +32,5 @@ sysinfo = { version = "0.29.10", features = ["serde"] }
|
||||
uuid = { version = "1.4.1", features = ["v4", "serde"] }
|
||||
lazy-regex = "3.0.2"
|
||||
thiserror = "1.0.49"
|
||||
image = "0.24.7"
|
||||
image = "0.24.7"
|
||||
rand = "0.8.5"
|
@ -1 +1,2 @@
|
||||
pub mod libvirt_actor;
|
||||
pub mod vnc_tokens_actor;
|
||||
|
108
virtweb_backend/src/actors/vnc_tokens_actor.rs
Normal file
108
virtweb_backend/src/actors/vnc_tokens_actor.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::libvirt_lib_structures::DomainXMLUuid;
|
||||
use crate::utils::rand_utils::rand_str;
|
||||
use crate::utils::time_utils::time;
|
||||
use actix::{Actor, Addr, AsyncContext, Context, Handler, Message};
|
||||
use std::time::Duration;
|
||||
|
||||
const TOKENS_CLEAN_INTERVAL: Duration = Duration::from_secs(60);
|
||||
const VNC_TOKEN_LEN: usize = 15;
|
||||
const VNC_TOKEN_LIFETIME: u64 = 120;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum VNCTokenError {
|
||||
#[error("Could not consume token, because it does not exist!")]
|
||||
ConsumeErrorTokenNotFound,
|
||||
#[error("Could not consume token, because it has expired!")]
|
||||
ConsumeErrorTokenExpired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct VNCToken {
|
||||
token: String,
|
||||
vm: DomainXMLUuid,
|
||||
expire: u64,
|
||||
}
|
||||
|
||||
impl VNCToken {
|
||||
fn is_expired(&self) -> bool {
|
||||
self.expire < time()
|
||||
}
|
||||
}
|
||||
|
||||
struct VNCTokensActor(Vec<VNCToken>);
|
||||
|
||||
impl Actor for VNCTokensActor {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
ctx.run_interval(TOKENS_CLEAN_INTERVAL, |act, _ctx| {
|
||||
// Regularly remove outdated entries
|
||||
log::debug!("Remove outdated VNC token entries");
|
||||
act.0.retain(|e| !e.is_expired())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "anyhow::Result<String>")]
|
||||
pub struct IssueTokenReq(DomainXMLUuid);
|
||||
|
||||
impl Handler<IssueTokenReq> for VNCTokensActor {
|
||||
type Result = anyhow::Result<String>;
|
||||
|
||||
fn handle(&mut self, msg: IssueTokenReq, _ctx: &mut Self::Context) -> Self::Result {
|
||||
log::debug!("Issue a new VNC token for domain {:?}", msg.0);
|
||||
let token = VNCToken {
|
||||
token: rand_str(VNC_TOKEN_LEN),
|
||||
vm: msg.0,
|
||||
expire: time() + VNC_TOKEN_LIFETIME,
|
||||
};
|
||||
self.0.push(token.clone());
|
||||
Ok(token.token)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "anyhow::Result<DomainXMLUuid>")]
|
||||
pub struct ConsumeTokenReq(String);
|
||||
|
||||
impl Handler<ConsumeTokenReq> for VNCTokensActor {
|
||||
type Result = anyhow::Result<DomainXMLUuid>;
|
||||
|
||||
fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result {
|
||||
log::debug!("Attempt to consume a token {:?}", msg.0);
|
||||
|
||||
let token_index = self
|
||||
.0
|
||||
.iter()
|
||||
.position(|i| i.token == msg.0)
|
||||
.ok_or(VNCTokenError::ConsumeErrorTokenNotFound)?;
|
||||
|
||||
let token = self.0.remove(token_index);
|
||||
|
||||
if token.is_expired() {
|
||||
return Err(VNCTokenError::ConsumeErrorTokenExpired.into());
|
||||
}
|
||||
|
||||
Ok(token.vm)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VNCTokensManager(Addr<VNCTokensActor>);
|
||||
|
||||
impl VNCTokensManager {
|
||||
pub fn start() -> Self {
|
||||
Self(VNCTokensActor(vec![]).start())
|
||||
}
|
||||
|
||||
/// Issue a new VNC access token for a domain
|
||||
pub async fn issue_token(&self, id: DomainXMLUuid) -> anyhow::Result<String> {
|
||||
self.0.send(IssueTokenReq(id)).await?
|
||||
}
|
||||
|
||||
/// Consume a VNC access token
|
||||
pub async fn consume_token(&self, token: String) -> anyhow::Result<DomainXMLUuid> {
|
||||
self.0.send(ConsumeTokenReq(token)).await?
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use crate::actors::vnc_tokens_actor::VNCTokensManager;
|
||||
use crate::controllers::{HttpResult, LibVirtReq};
|
||||
use crate::libvirt_lib_structures::{DomainState, DomainXMLUuid};
|
||||
use crate::libvirt_rest_structures::VMInfo;
|
||||
@ -208,3 +209,25 @@ pub async fn screenshot(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> H
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct IssueVNCTokenResponse {
|
||||
token: String,
|
||||
}
|
||||
|
||||
/// Issue a VNC connection token
|
||||
pub async fn vnc_token(
|
||||
manager: web::Data<VNCTokensManager>,
|
||||
id: web::Path<SingleVMUUidReq>,
|
||||
) -> HttpResult {
|
||||
Ok(match manager.issue_token(id.uid).await {
|
||||
Ok(token) => HttpResponse::Ok().json(IssueVNCTokenResponse { token }),
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"Failed to issue a token for a domain domain {:?} ! {e}",
|
||||
id.uid
|
||||
);
|
||||
HttpResponse::InternalServerError().json("Failed to issue a token for the domain!")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use actix_web::{web, App, HttpServer};
|
||||
use light_openid::basic_state_manager::BasicStateManager;
|
||||
use std::time::Duration;
|
||||
use virtweb_backend::actors::libvirt_actor::LibVirtActor;
|
||||
use virtweb_backend::actors::vnc_tokens_actor::VNCTokensManager;
|
||||
use virtweb_backend::app_config::AppConfig;
|
||||
use virtweb_backend::constants;
|
||||
use virtweb_backend::constants::{
|
||||
@ -41,6 +42,8 @@ async fn main() -> std::io::Result<()> {
|
||||
.start(),
|
||||
));
|
||||
|
||||
let vnc_tokens = Data::new(VNCTokensManager::start());
|
||||
|
||||
log::info!("Start to listen on {}", AppConfig::get().listen_address);
|
||||
|
||||
let state_manager = Data::new(BasicStateManager::new());
|
||||
@ -78,6 +81,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.wrap(session_mw)
|
||||
.wrap(cors)
|
||||
.app_data(state_manager.clone())
|
||||
.app_data(vnc_tokens.clone())
|
||||
.app_data(Data::new(RemoteIPConfig {
|
||||
proxy: AppConfig::get().proxy_ip.clone(),
|
||||
}))
|
||||
@ -157,6 +161,10 @@ async fn main() -> std::io::Result<()> {
|
||||
"/api/vm/{uid}/screenshot",
|
||||
web::get().to(vm_controller::screenshot),
|
||||
)
|
||||
.route(
|
||||
"/api/vm/{uid}/vnc_token",
|
||||
web::get().to(vm_controller::vnc_token),
|
||||
)
|
||||
})
|
||||
.bind(&AppConfig::get().listen_address)?
|
||||
.run()
|
||||
|
@ -1,2 +1,4 @@
|
||||
pub mod files_utils;
|
||||
pub mod rand_utils;
|
||||
pub mod time_utils;
|
||||
pub mod url_utils;
|
||||
|
12
virtweb_backend/src/utils/rand_utils.rs
Normal file
12
virtweb_backend/src/utils/rand_utils.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::Rng;
|
||||
|
||||
/// Generate a random string
|
||||
pub fn rand_str(len: usize) -> String {
|
||||
let s: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(len)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
s
|
||||
}
|
15
virtweb_backend/src/utils/time_utils.rs
Normal file
15
virtweb_backend/src/utils/time_utils.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Get the current time since epoch
|
||||
///
|
||||
/// ```
|
||||
/// use virtweb_backend::utils::time_utils::time;
|
||||
///
|
||||
/// let time = time();
|
||||
/// ```
|
||||
pub fn time() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
@ -25,7 +25,7 @@ export function VMScreenshot(p: { vm: VMInfo }): React.ReactElement {
|
||||
|
||||
if (int.current === undefined) {
|
||||
refresh();
|
||||
int.current = setInterval(() => refresh(), 5000000);
|
||||
int.current = setInterval(() => refresh(), 5000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
Loading…
Reference in New Issue
Block a user