///! Daemon binary to run inside a micro-VM for secure single file restore of disk images use std::fs::File; use std::io::prelude::*; use std::os::unix::{ io::{FromRawFd, RawFd}, net, }; use std::path::Path; use std::sync::{Arc, Mutex}; use std::future::Future; use std::pin::Pin; use anyhow::{bail, format_err, Error}; use lazy_static::lazy_static; use log::{error, info}; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use http::request::Parts; use http::Response; use hyper::{Body, StatusCode}; use hyper::header; use proxmox::api::RpcEnvironmentType; use pbs_client::DEFAULT_VSOCK_PORT; use proxmox_rest_server::{ApiConfig, RestServer}; mod proxmox_restore_daemon; use proxmox_restore_daemon::*; /// Maximum amount of pending requests. If saturated, virtio-vsock returns ETIMEDOUT immediately. /// We should never have more than a few requests in queue, so use a low number. pub const MAX_PENDING: usize = 32; /// Will be present in base initramfs pub const VM_DETECT_FILE: &str = "/restore-vm-marker"; lazy_static! { /// The current disks state. Use for accessing data on the attached snapshots. pub static ref DISK_STATE: Arc> = { Arc::new(Mutex::new(DiskState::scan().unwrap())) }; } /// This is expected to be run by 'proxmox-file-restore' within a mini-VM fn main() -> Result<(), Error> { if !Path::new(VM_DETECT_FILE).exists() { bail!( "This binary is not supposed to be run manually, use 'proxmox-file-restore' instead." ); } // don't have a real syslog (and no persistance), so use env_logger to print to a log file (via // stdout to a serial terminal attached by QEMU) env_logger::from_env(env_logger::Env::default().default_filter_or("info")) .write_style(env_logger::WriteStyle::Never) .format_timestamp_millis() .init(); info!("setup basic system environment..."); setup_system_env().map_err(|err| format_err!("system environment setup failed: {}", err))?; // scan all attached disks now, before starting the API // this will panic and stop the VM if anything goes wrong info!("scanning all disks..."); { let _disk_state = DISK_STATE.lock().unwrap(); } info!("disk scan complete, starting main runtime..."); pbs_runtime::main(run()) } /// ensure we have our /run dirs, system users and stuff like that setup fn setup_system_env() -> Result<(), Error> { // the API may save some stuff there, e.g., the memcon tracking file // we do not care much, but it's way less headache to just create it std::fs::create_dir_all("/run/proxmox-backup")?; // we now ensure that all lock files are owned by the backup user, and as we reuse the // specialized REST module from pbs api/daemon we have some checks there for user/acl stuff // that gets locked, and thus needs the backup system user to work. std::fs::create_dir_all("/etc")?; let mut passwd = File::create("/etc/passwd")?; writeln!(passwd, "root:x:0:0:root:/root:/bin/sh")?; writeln!(passwd, "backup:x:34:34:backup:/var/backups:/usr/sbin/nologin")?; let mut group = File::create("/etc/group")?; writeln!(group, "root:x:0:")?; writeln!(group, "backup:x:34:")?; Ok(()) } fn get_index<'a>( _auth_id: Option, _language: Option, _api: &'a ApiConfig, _parts: Parts, ) -> Pin> + Send + 'a>> { Box::pin(async move { let index = "

Proxmox Backup Restore Daemon/h1>

"; Response::builder() .status(StatusCode::OK) .header(header::CONTENT_TYPE, "text/html") .body(index.into()) .unwrap() }) } async fn run() -> Result<(), Error> { watchdog_init(); let auth_config = Arc::new( auth::ticket_auth().map_err(|err| format_err!("reading ticket file failed: {}", err))?, ); let config = ApiConfig::new("", &ROUTER, RpcEnvironmentType::PUBLIC, auth_config, &get_index)?; let rest_server = RestServer::new(config); let vsock_fd = get_vsock_fd()?; let connections = accept_vsock_connections(vsock_fd); let receiver_stream = ReceiverStream::new(connections); let acceptor = hyper::server::accept::from_stream(receiver_stream); hyper::Server::builder(acceptor).serve(rest_server).await?; bail!("hyper server exited"); } fn accept_vsock_connections( vsock_fd: RawFd, ) -> mpsc::Receiver> { use nix::sys::socket::*; let (sender, receiver) = mpsc::channel(MAX_PENDING); tokio::spawn(async move { loop { let stream: Result = tokio::task::block_in_place(|| { // we need to accept manually, as UnixListener aborts if socket type != AF_UNIX ... let client_fd = accept(vsock_fd)?; let stream = unsafe { net::UnixStream::from_raw_fd(client_fd) }; stream.set_nonblocking(true)?; tokio::net::UnixStream::from_std(stream).map_err(|err| err.into()) }); match stream { Ok(stream) => { if sender.send(Ok(stream)).await.is_err() { error!("connection accept channel was closed"); } } Err(err) => { error!("error accepting vsock connetion: {}", err); } } } }); receiver } fn get_vsock_fd() -> Result { use nix::sys::socket::*; let sock_fd = socket( AddressFamily::Vsock, SockType::Stream, SockFlag::empty(), None, )?; let sock_addr = VsockAddr::new(libc::VMADDR_CID_ANY, DEFAULT_VSOCK_PORT as u32); bind(sock_fd, &SockAddr::Vsock(sock_addr))?; listen(sock_fd, MAX_PENDING)?; Ok(sock_fd) }