file-restore-daemon: add binary with virtio-vsock API server

Implements the base of a small daemon to run within a file-restore VM.

The binary spawns an API server on a virtio-vsock socket, listening for
connections from the host. This happens mostly manually via the standard
Unix socket API, since tokio/hyper do not have support for vsock built
in. Once we have the accept'ed file descriptor, we can create a
UnixStream and use our tower service implementation for that.

The binary is deliberately not installed in the usual $PATH location,
since it shouldn't be executed on the host by a user anyway.

For now, only the API calls 'status' and 'stop' are implemented, to
demonstrate and test proxmox::api functionality.

Authorization is provided via a custom ApiAuth only checking a header
value against a static /ticket file.

Since the REST server implementation uses the log!() macro, we can
redirect its output to stdout by registering env_logger as the logging
target. env_logger is already in our dependency tree via zstd/bindgen.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Stefan Reiter
2021-03-31 12:21:52 +02:00
committed by Thomas Lamprecht
parent 26858dba84
commit dd9cef56fc
10 changed files with 246 additions and 1 deletions

View File

@ -0,0 +1,62 @@
///! File-restore API running inside the restore VM
use anyhow::Error;
use serde_json::Value;
use std::fs;
use proxmox::api::{api, ApiMethod, Permission, Router, RpcEnvironment, SubdirMap};
use proxmox::list_subdirs_api_method;
use proxmox_backup::api2::types::*;
// NOTE: All API endpoints must have Permission::Superuser, as the configs for authentication do
// not exist within the restore VM. Safety is guaranteed by checking a ticket via a custom ApiAuth.
const SUBDIRS: SubdirMap = &[
("status", &Router::new().get(&API_METHOD_STATUS)),
("stop", &Router::new().get(&API_METHOD_STOP)),
];
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))
.subdirs(SUBDIRS);
fn read_uptime() -> Result<f32, Error> {
let uptime = fs::read_to_string("/proc/uptime")?;
// unwrap the Option, if /proc/uptime is empty we have bigger problems
Ok(uptime.split_ascii_whitespace().next().unwrap().parse()?)
}
#[api(
access: {
description: "Permissions are handled outside restore VM.",
permission: &Permission::Superuser,
},
returns: {
type: RestoreDaemonStatus,
}
)]
/// General status information
fn status(
_param: Value,
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<RestoreDaemonStatus, Error> {
Ok(RestoreDaemonStatus {
uptime: read_uptime()? as i64,
})
}
#[api(
access: {
description: "Permissions are handled outside restore VM.",
permission: &Permission::Superuser,
},
)]
/// Stop the restore VM immediately, this will never return if successful
fn stop() {
use nix::sys::reboot;
println!("/stop called, shutting down");
let err = reboot::reboot(reboot::RebootMode::RB_POWER_OFF).unwrap_err();
println!("'reboot' syscall failed: {}", err);
std::process::exit(1);
}

View File

@ -0,0 +1,45 @@
//! Authentication via a static ticket file
use anyhow::{bail, format_err, Error};
use std::fs::File;
use std::io::prelude::*;
use proxmox_backup::api2::types::Authid;
use proxmox_backup::config::cached_user_info::CachedUserInfo;
use proxmox_backup::server::auth::{ApiAuth, AuthError};
const TICKET_FILE: &str = "/ticket";
pub struct StaticAuth {
ticket: String,
}
impl ApiAuth for StaticAuth {
fn check_auth(
&self,
headers: &http::HeaderMap,
_method: &hyper::Method,
_user_info: &CachedUserInfo,
) -> Result<Authid, AuthError> {
match headers.get(hyper::header::AUTHORIZATION) {
Some(header) if header.to_str().unwrap_or("") == &self.ticket => {
Ok(Authid::root_auth_id().to_owned())
}
_ => {
return Err(AuthError::Generic(format_err!(
"invalid file restore ticket provided"
)));
}
}
}
}
pub fn ticket_auth() -> Result<StaticAuth, Error> {
let mut ticket_file = File::open(TICKET_FILE)?;
let mut ticket = String::new();
let len = ticket_file.read_to_string(&mut ticket)?;
if len <= 0 {
bail!("invalid ticket: cannot be empty");
}
Ok(StaticAuth { ticket })
}

View File

@ -0,0 +1,5 @@
///! File restore VM related functionality
mod api;
pub use api::*;
pub mod auth;