From b4975d3102e317bf1beb443eaed6fac5c7431611 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 5 Mar 2021 11:40:52 +0100 Subject: [PATCH] tape: finish api permission checks --- src/api2/tape/backup.rs | 55 +++++++++++++++++++++++++++++++++++++++ src/api2/tape/changer.rs | 7 +++++ src/api2/tape/drive.rs | 56 ++++++++++++++++++++++++++++++++++++++++ src/api2/tape/restore.rs | 37 +++++++++++++++++++++++--- 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/src/api2/tape/backup.rs b/src/api2/tape/backup.rs index 2fa4f352..66a89d51 100644 --- a/src/api2/tape/backup.rs +++ b/src/api2/tape/backup.rs @@ -20,7 +20,9 @@ use crate::{ self, cached_user_info::CachedUserInfo, acl::{ + PRIV_DATASTORE_READ, PRIV_TAPE_AUDIT, + PRIV_TAPE_WRITE, }, tape_job::{ TapeBackupJobConfig, @@ -71,6 +73,33 @@ pub const ROUTER: Router = Router::new() .post(&API_METHOD_BACKUP) .match_all("id", &TAPE_BACKUP_JOB_ROUTER); +fn check_backup_permission( + auth_id: &Authid, + store: &str, + pool: &str, + drive: &str, +) -> Result<(), Error> { + + let user_info = CachedUserInfo::new()?; + + let privs = user_info.lookup_privs(auth_id, &["datastore", store]); + if (privs & PRIV_DATASTORE_READ) == 0 { + bail!("no permissions on /datastore/{}", store); + } + + let privs = user_info.lookup_privs(auth_id, &["tape", "drive", drive]); + if (privs & PRIV_TAPE_WRITE) == 0 { + bail!("no permissions on /tape/drive/{}", drive); + } + + let privs = user_info.lookup_privs(auth_id, &["tape", "pool", pool]); + if (privs & PRIV_TAPE_WRITE) == 0 { + bail!("no permissions on /tape/pool/{}", pool); + } + + Ok(()) +} + #[api( returns: { description: "List configured thape backup jobs and their status", @@ -202,6 +231,12 @@ pub fn do_tape_backup_job( }, }, }, + access: { + // Note: parameters are from job config, so we need to test inside function body + description: "The user needs Tape.Write privilege on /tape/pool/{pool} \ + and /tape/drive/{drive}, Datastore.Read privilege on /datastore/{store}.", + permission: &Permission::Anybody, + }, )] /// Runs a tape backup job manually. pub fn run_tape_backup_job( @@ -213,6 +248,13 @@ pub fn run_tape_backup_job( let (config, _digest) = config::tape_job::config()?; let backup_job: TapeBackupJobConfig = config.lookup("backup", &id)?; + check_backup_permission( + &auth_id, + &backup_job.setup.store, + &backup_job.setup.pool, + &backup_job.setup.drive, + )?; + let job = Job::new("tape-backup-job", &id)?; let upid_str = do_tape_backup_job(job, backup_job.setup, &auth_id, None)?; @@ -232,6 +274,12 @@ pub fn run_tape_backup_job( returns: { schema: UPID_SCHEMA, }, + access: { + // Note: parameters are no uri parameter, so we need to test inside function body + description: "The user needs Tape.Write privilege on /tape/pool/{pool} \ + and /tape/drive/{drive}, Datastore.Read privilege on /datastore/{store}.", + permission: &Permission::Anybody, + }, )] /// Backup datastore to tape media pool pub fn backup( @@ -241,6 +289,13 @@ pub fn backup( let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + check_backup_permission( + &auth_id, + &setup.store, + &setup.pool, + &setup.drive, + )?; + let datastore = DataStore::lookup_datastore(&setup.store)?; let (config, _digest) = config::media_pool::config()?; diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs index be9e76c2..e2d1edc0 100644 --- a/src/api2/tape/changer.rs +++ b/src/api2/tape/changer.rs @@ -13,6 +13,7 @@ use crate::{ cached_user_info::CachedUserInfo, acl::{ PRIV_TAPE_AUDIT, + PRIV_TAPE_READ, }, }, api2::types::{ @@ -60,6 +61,9 @@ use crate::{ type: MtxStatusEntry, }, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false), + }, )] /// Get tape changer status pub async fn get_status( @@ -156,6 +160,9 @@ pub async fn get_status( }, }, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_READ, false), + }, )] /// Transfers media from one slot to another pub async fn transfer( diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 4d901e8b..1788762e 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -29,6 +29,8 @@ use crate::{ cached_user_info::CachedUserInfo, acl::{ PRIV_TAPE_AUDIT, + PRIV_TAPE_READ, + PRIV_TAPE_WRITE, }, }, api2::{ @@ -143,6 +145,9 @@ where returns: { schema: UPID_SCHEMA, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ, false), + }, )] /// Load media with specified label /// @@ -182,6 +187,9 @@ pub fn load_media( }, }, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ, false), + }, )] /// Load media from the specified slot /// @@ -215,6 +223,9 @@ pub async fn load_slot(drive: String, source_slot: u64) -> Result<(), Error> { type: u64, minimum: 1, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ, false), + }, )] /// Export media with specified label pub async fn export_media(drive: String, label_text: String) -> Result { @@ -252,6 +263,9 @@ pub async fn export_media(drive: String, label_text: String) -> Result Result, Error> { @@ -1047,6 +1094,9 @@ pub async fn cartridge_memory(drive: String) -> Result, Error> returns: { type: Lp17VolumeStatistics, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT, false), + }, )] /// Read Volume Statistics (SCSI log page 17h) pub async fn volume_statistics(drive: String) -> Result { @@ -1074,6 +1124,9 @@ pub async fn volume_statistics(drive: String) -> Result Result { @@ -1115,6 +1168,9 @@ pub async fn status(drive: String) -> Result { returns: { schema: UPID_SCHEMA, }, + access: { + permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ, false), + }, )] /// Scan media and record content pub fn catalog_media( diff --git a/src/api2/tape/restore.rs b/src/api2/tape/restore.rs index e506c36c..22da07b8 100644 --- a/src/api2/tape/restore.rs +++ b/src/api2/tape/restore.rs @@ -11,6 +11,7 @@ use proxmox::{ RpcEnvironment, RpcEnvironmentType, Router, + Permission, section_config::SectionConfigData, }, tools::{ @@ -33,7 +34,14 @@ use crate::{ UPID_SCHEMA, Authid, }, - config, + config::{ + self, + cached_user_info::CachedUserInfo, + acl::{ + PRIV_DATASTORE_BACKUP, + PRIV_TAPE_READ, + }, + }, backup::{ archive_type, MANIFEST_BLOB_NAME, @@ -76,7 +84,6 @@ use crate::{ pub const ROUTER: Router = Router::new() .post(&API_METHOD_RESTORE); - #[api( input: { properties: { @@ -95,6 +102,12 @@ pub const ROUTER: Router = Router::new() returns: { schema: UPID_SCHEMA, }, + access: { + // Note: parameters are no uri parameter, so we need to test inside function body + description: "The user needs Tape.Read privilege on /tape/pool/{pool} \ + and /tape/drive/{drive}, Datastore.Backup privilege on /datastore/{store}.", + permission: &Permission::Anybody, + }, )] /// Restore data from media-set pub fn restore( @@ -104,9 +117,18 @@ pub fn restore( rpcenv: &mut dyn RpcEnvironment, ) -> Result { - let datastore = DataStore::lookup_datastore(&store)?; - let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + let privs = user_info.lookup_privs(&auth_id, &["datastore", &store]); + if (privs & PRIV_DATASTORE_BACKUP) == 0 { + bail!("no permissions on /datastore/{}", store); + } + + let privs = user_info.lookup_privs(&auth_id, &["tape", "drive", &drive]); + if (privs & PRIV_TAPE_READ) == 0 { + bail!("no permissions on /tape/drive/{}", drive); + } let status_path = Path::new(TAPE_STATUS_DIR); let inventory = Inventory::load(status_path)?; @@ -115,6 +137,13 @@ pub fn restore( let pool = inventory.lookup_media_set_pool(&media_set_uuid)?; + let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", &pool]); + if (privs & PRIV_TAPE_READ) == 0 { + bail!("no permissions on /tape/pool/{}", pool); + } + + let datastore = DataStore::lookup_datastore(&store)?; + let (drive_config, _digest) = config::drive::config()?; // early check/lock before starting worker