move and unify namespace priv helpers

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Fabian Grünbichler 2022-05-25 10:07:54 +02:00 committed by Thomas Lamprecht
parent 77bd14f68a
commit ea2e91e52f
5 changed files with 208 additions and 182 deletions

View File

@ -32,14 +32,14 @@ use pxar::accessor::aio::Accessor;
use pxar::EntryKind; use pxar::EntryKind;
use pbs_api_types::{ use pbs_api_types::{
print_ns_and_snapshot, privs_to_priv_names, Authid, BackupContent, BackupNamespace, BackupType, print_ns_and_snapshot, Authid, BackupContent, BackupNamespace, BackupType, Counts, CryptMode,
Counts, CryptMode, DataStoreListItem, DataStoreStatus, DatastoreWithNamespace, DataStoreListItem, DataStoreStatus, DatastoreWithNamespace, GarbageCollectionStatus,
GarbageCollectionStatus, GroupListItem, Operation, PruneOptions, RRDMode, RRDTimeFrame, GroupListItem, Operation, PruneOptions, RRDMode, RRDTimeFrame, SnapshotListItem,
SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA,
BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA,
IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY,
PRIV_DATASTORE_VERIFY, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
}; };
use pbs_client::pxar::{create_tar, create_zip}; use pbs_client::pxar::{create_tar, create_zip};
use pbs_config::CachedUserInfo; use pbs_config::CachedUserInfo;
@ -63,7 +63,7 @@ use proxmox_rest_server::{formatter, WorkerTask};
use crate::api2::backup::optional_ns_param; use crate::api2::backup::optional_ns_param;
use crate::api2::node::rrd::create_value_from_rrd; use crate::api2::node::rrd::create_value_from_rrd;
use crate::backup::{ use crate::backup::{
verify_all_backups, verify_backup_dir, verify_backup_group, verify_filter, check_ns_privs_full, verify_all_backups, verify_backup_dir, verify_backup_group, verify_filter,
ListAccessibleBackupGroups, ListAccessibleBackupGroups,
}; };
@ -81,63 +81,29 @@ fn get_group_note_path(
note_path note_path
} }
// TODO: move somewhere we can reuse it from (namespace has its own copy atm.)
fn get_ns_privs(store_with_ns: &DatastoreWithNamespace, auth_id: &Authid) -> Result<u64, Error> {
let user_info = CachedUserInfo::new()?;
Ok(user_info.lookup_privs(auth_id, &store_with_ns.acl_path()))
}
// asserts that either either `full_access_privs` or `partial_access_privs` are fulfilled,
// returning value indicates whether further checks like group ownerships are required
fn check_ns_privs(
store: &str,
ns: &BackupNamespace,
auth_id: &Authid,
full_access_privs: u64,
partial_access_privs: u64,
) -> Result<bool, Error> {
let store_with_ns = DatastoreWithNamespace {
store: store.to_string(),
ns: ns.clone(),
};
let privs = get_ns_privs(&store_with_ns, auth_id)?;
if full_access_privs != 0 && (privs & full_access_privs) != 0 {
return Ok(false);
}
if partial_access_privs != 0 && (privs & partial_access_privs) != 0 {
return Ok(true);
}
let priv_names = privs_to_priv_names(full_access_privs | partial_access_privs).join("|");
let path = format!("/{}", store_with_ns.acl_path().join("/"));
proxmox_router::http_bail!(
FORBIDDEN,
"permission check failed - missing {priv_names} on {path}"
);
}
// helper to unify common sequence of checks: // helper to unify common sequence of checks:
// 1. check privs on NS (full or limited access) // 1. check privs on NS (full or limited access)
// 2. load datastore // 2. load datastore
// 3. if needed (only limited access), check owner of group // 3. if needed (only limited access), check owner of group
fn check_privs_and_load_store( fn check_privs_and_load_store(
store: &str, store_with_ns: &DatastoreWithNamespace,
ns: &BackupNamespace,
auth_id: &Authid, auth_id: &Authid,
full_access_privs: u64, full_access_privs: u64,
partial_access_privs: u64, partial_access_privs: u64,
operation: Option<Operation>, operation: Option<Operation>,
backup_group: &pbs_api_types::BackupGroup, backup_group: &pbs_api_types::BackupGroup,
) -> Result<Arc<DataStore>, Error> { ) -> Result<Arc<DataStore>, Error> {
let limited = check_ns_privs(store, ns, auth_id, full_access_privs, partial_access_privs)?; let limited = check_ns_privs_full(
store_with_ns,
auth_id,
full_access_privs,
partial_access_privs,
)?;
let datastore = DataStore::lookup_datastore(&store, operation)?; let datastore = DataStore::lookup_datastore(&store_with_ns.store, operation)?;
if limited { if limited {
let owner = datastore.get_owner(&ns, backup_group)?; let owner = datastore.get_owner(&store_with_ns.ns, backup_group)?;
check_backup_owner(&owner, &auth_id)?; check_backup_owner(&owner, &auth_id)?;
} }
@ -222,19 +188,19 @@ pub fn list_groups(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let list_all = !check_ns_privs( let store_with_ns = DatastoreWithNamespace {
&store, store: store.clone(),
&ns, ns: ns.clone(),
};
let list_all = !check_ns_privs_full(
&store_with_ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
)?; )?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?; let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let store_with_ns = DatastoreWithNamespace {
store: store.to_owned(),
ns: ns.clone(),
};
datastore datastore
.iter_backup_groups(ns.clone())? // FIXME: Namespaces and recursion parameters! .iter_backup_groups(ns.clone())? // FIXME: Namespaces and recursion parameters!
@ -327,8 +293,10 @@ pub fn delete_group(
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &DatastoreWithNamespace {
&ns, store: store.clone(),
ns: ns.clone(),
},
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_PRUNE,
@ -375,10 +343,13 @@ pub fn list_snapshot_files(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ, PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -426,9 +397,13 @@ pub fn delete_snapshot(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_PRUNE,
@ -482,20 +457,19 @@ pub fn list_snapshots(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let list_all = !check_ns_privs( let list_all = !check_ns_privs_full(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
)?; )?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?; let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let store_with_ns = DatastoreWithNamespace {
store: store.to_owned(),
ns: ns.clone(),
};
// FIXME: filter also owner before collecting, for doing that nicely the owner should move into // FIXME: filter also owner before collecting, for doing that nicely the owner should move into
// backup group and provide an error free (Err -> None) accessor // backup group and provide an error free (Err -> None) accessor
@ -774,9 +748,13 @@ pub fn verify(
) -> Result<Value, Error> { ) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let owner_check_required = check_ns_privs( let store_with_ns = DatastoreWithNamespace {
&store, store: store.clone(),
&ns, ns: ns.clone(),
};
let owner_check_required = check_ns_privs_full(
&store_with_ns,
&auth_id, &auth_id,
PRIV_DATASTORE_VERIFY, PRIV_DATASTORE_VERIFY,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -947,19 +925,19 @@ pub fn prune(
) -> Result<Value, Error> { ) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_PRUNE,
Some(Operation::Write), Some(Operation::Write),
&group, &group,
)?; )?;
let store_with_ns = DatastoreWithNamespace {
store: store.to_owned(),
ns: ns.clone(),
};
let worker_id = format!("{}:{}:{}", store, ns, group); let worker_id = format!("{}:{}:{}", store, ns, group);
let group = datastore.backup_group(ns, group); let group = datastore.backup_group(ns, group);
@ -1307,8 +1285,7 @@ pub fn download_file(
}; };
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?; let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&backup_ns,
&auth_id, &auth_id,
PRIV_DATASTORE_READ, PRIV_DATASTORE_READ,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1392,8 +1369,7 @@ pub fn download_file_decoded(
}; };
let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?; let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&backup_ns,
&auth_id, &auth_id,
PRIV_DATASTORE_READ, PRIV_DATASTORE_READ,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1523,8 +1499,7 @@ pub fn upload_backup_log(
let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?; let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&backup_ns,
&auth_id, &auth_id,
0, 0,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1597,9 +1572,13 @@ pub fn catalog(
) -> Result<Vec<ArchiveEntry>, Error> { ) -> Result<Vec<ArchiveEntry>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_READ, PRIV_DATASTORE_READ,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1676,10 +1655,13 @@ pub fn pxar_file_download(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?; let store = required_string_param(&param, "store")?;
let ns = optional_ns_param(&param)?; let ns = optional_ns_param(&param)?;
let store_with_ns = DatastoreWithNamespace {
store: store.to_owned(),
ns: ns.clone(),
};
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?; let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_READ, PRIV_DATASTORE_READ,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1883,9 +1865,13 @@ pub fn get_group_notes(
) -> Result<String, Error> { ) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1930,9 +1916,12 @@ pub fn set_group_notes(
) -> Result<(), Error> { ) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -1975,9 +1964,13 @@ pub fn get_notes(
) -> Result<String, Error> { ) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -2027,9 +2020,13 @@ pub fn set_notes(
) -> Result<(), Error> { ) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -2077,9 +2074,12 @@ pub fn get_protection(
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -2125,9 +2125,12 @@ pub fn set_protection(
) -> Result<(), Error> { ) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let store_with_ns = DatastoreWithNamespace {
store: store.clone(),
ns: ns.clone(),
};
let datastore = check_privs_and_load_store( let datastore = check_privs_and_load_store(
&store, &store_with_ns,
&ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,
@ -2173,9 +2176,12 @@ pub fn set_backup_owner(
) -> Result<(), Error> { ) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let ns = ns.unwrap_or_default(); let ns = ns.unwrap_or_default();
let owner_check_required = check_ns_privs( let store_with_ns = DatastoreWithNamespace {
&store, store: store.clone(),
&ns, ns: ns.clone(),
};
let owner_check_required = check_ns_privs_full(
&store_with_ns,
&auth_id, &auth_id,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_MODIFY,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_BACKUP,

View File

@ -2,7 +2,7 @@ use anyhow::{bail, Error};
use serde_json::Value; use serde_json::Value;
use pbs_config::CachedUserInfo; use pbs_config::CachedUserInfo;
use proxmox_router::{http_err, ApiMethod, Permission, Router, RpcEnvironment}; use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::*; use proxmox_schema::*;
use pbs_api_types::{ use pbs_api_types::{
@ -13,18 +13,7 @@ use pbs_api_types::{
use pbs_datastore::DataStore; use pbs_datastore::DataStore;
// TODO: move somewhere we can reuse it from (datastore has its own copy atm.) use crate::backup::{check_ns_modification_privs, check_ns_privs};
fn check_ns_privs(
store_with_ns: &DatastoreWithNamespace,
auth_id: &Authid,
privs: u64,
) -> Result<(), Error> {
let user_info = CachedUserInfo::new()?;
user_info
.check_privs(auth_id, &store_with_ns.acl_path(), privs, true)
.map_err(|err| http_err!(FORBIDDEN, "{err}"))
}
#[api( #[api(
input: { input: {
@ -62,12 +51,15 @@ pub fn create_namespace(
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let parent = parent.unwrap_or_default(); let parent = parent.unwrap_or_default();
let store_with_parent = DatastoreWithNamespace { let mut ns = parent.clone();
ns.push(name.clone())?;
let store_with_ns = DatastoreWithNamespace {
store: store.clone(), store: store.clone(),
ns: parent.clone(), ns,
}; };
check_ns_privs(&store_with_parent, &auth_id, PRIV_DATASTORE_MODIFY)?; check_ns_modification_privs(&store_with_ns, &auth_id)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?; let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
@ -165,17 +157,12 @@ pub fn delete_namespace(
_info: &ApiMethod, _info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment, rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
// we could allow it as easy purge-whole datastore, but lets be more restrictive for now
if ns.is_root() {
bail!("cannot delete root namespace!");
};
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let parent = ns.parent(); // must have MODIFY permission on parent to allow deletion let store_with_ns = DatastoreWithNamespace {
let store_with_parent = DatastoreWithNamespace {
store: store.clone(), store: store.clone(),
ns: parent.clone(), ns: ns.clone(),
}; };
check_ns_privs(&store_with_parent, &auth_id, PRIV_DATASTORE_MODIFY)?; check_ns_modification_privs(&store_with_ns, &auth_id)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?; let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;

View File

@ -34,6 +34,7 @@ use pbs_tape::{
}; };
use proxmox_rest_server::WorkerTask; use proxmox_rest_server::WorkerTask;
use crate::backup::check_ns_modification_privs;
use crate::{ use crate::{
server::lookup_user_email, server::lookup_user_email,
tape::{ tape::{
@ -237,25 +238,16 @@ fn check_and_create_namespaces(
// try create recursively if it does not exist // try create recursively if it does not exist
if !store.namespace_exists(ns) { if !store.namespace_exists(ns) {
let mut tmp_ns: BackupNamespace = Default::default(); store_with_ns.ns = Default::default();
for comp in ns.components() { for comp in ns.components() {
tmp_ns.push(comp.to_string())?; store_with_ns.ns.push(comp.to_string())?;
if !store.namespace_exists(&tmp_ns) { if !store.namespace_exists(&store_with_ns.ns) {
// check parent modification privs check_ns_modification_privs(&store_with_ns, auth_id).map_err(|_err| {
user_info format_err!("no permission to create namespace '{}'", store_with_ns.ns)
.check_privs( })?;
auth_id,
&store_with_ns.acl_path(),
PRIV_DATASTORE_MODIFY,
false,
)
.map_err(|_err| format_err!("no permission to create namespace '{tmp_ns}'"))?;
store.create_namespace(&tmp_ns.parent(), comp.to_string())?; store.create_namespace(&store_with_ns.ns.parent(), comp.to_string())?;
// update parent for next component
store_with_ns.ns = tmp_ns.clone();
} }
} }
} }

View File

@ -1,14 +1,72 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Error; use anyhow::{bail, Error};
use pbs_api_types::{ use pbs_api_types::{
Authid, BackupNamespace, DatastoreWithNamespace, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, privs_to_priv_names, Authid, BackupNamespace, DatastoreWithNamespace, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_READ, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_READ,
}; };
use pbs_config::CachedUserInfo; use pbs_config::CachedUserInfo;
use pbs_datastore::{backup_info::BackupGroup, DataStore, ListGroups, ListNamespacesRecursive}; use pbs_datastore::{backup_info::BackupGroup, DataStore, ListGroups, ListNamespacesRecursive};
/// Asserts that `privs` are fulfilled on datastore + (optional) namespace.
pub fn check_ns_privs(
store_with_ns: &DatastoreWithNamespace,
auth_id: &Authid,
privs: u64,
) -> Result<(), Error> {
check_ns_privs_full(store_with_ns, auth_id, privs, 0).map(|_| ())
}
/// Asserts that `privs` for creating/destroying namespace in datastore are fulfilled.
pub fn check_ns_modification_privs(
store_with_ns: &DatastoreWithNamespace,
auth_id: &Authid,
) -> Result<(), Error> {
// we could allow it as easy purge-whole datastore, but lets be more restrictive for now
if store_with_ns.ns.is_root() {
// TODO
bail!("Cannot create/delete root namespace!");
}
let parent = DatastoreWithNamespace {
store: store_with_ns.store.clone(),
ns: store_with_ns.ns.parent(),
};
check_ns_privs(&parent, auth_id, PRIV_DATASTORE_MODIFY)
}
/// Asserts that either either `full_access_privs` or `partial_access_privs` are fulfilled on
/// datastore + (optional) namespace.
///
/// Return value indicates whether further checks like group ownerships are required because
/// `full_access_privs` are missing.
pub fn check_ns_privs_full(
store_with_ns: &DatastoreWithNamespace,
auth_id: &Authid,
full_access_privs: u64,
partial_access_privs: u64,
) -> Result<bool, Error> {
let user_info = CachedUserInfo::new()?;
let privs = user_info.lookup_privs(auth_id, &store_with_ns.acl_path());
if full_access_privs != 0 && (privs & full_access_privs) != 0 {
return Ok(false);
}
if partial_access_privs != 0 && (privs & partial_access_privs) != 0 {
return Ok(true);
}
let priv_names = privs_to_priv_names(full_access_privs | partial_access_privs).join("|");
let path = format!("/{}", store_with_ns.acl_path().join("/"));
proxmox_router::http_bail!(
FORBIDDEN,
"permission check failed - missing {priv_names} on {path}"
);
}
/// A priviledge aware iterator for all backup groups in all Namespaces below an anchor namespace, /// A priviledge aware iterator for all backup groups in all Namespaces below an anchor namespace,
/// most often that will be the `BackupNamespace::root()` one. /// most often that will be the `BackupNamespace::root()` one.
/// ///

View File

@ -18,7 +18,7 @@ use proxmox_sys::task_log;
use pbs_api_types::{ use pbs_api_types::{
Authid, BackupNamespace, DatastoreWithNamespace, GroupFilter, GroupListItem, NamespaceListItem, Authid, BackupNamespace, DatastoreWithNamespace, GroupFilter, GroupListItem, NamespaceListItem,
Operation, RateLimitConfig, Remote, SnapshotListItem, MAX_NAMESPACE_DEPTH, Operation, RateLimitConfig, Remote, SnapshotListItem, MAX_NAMESPACE_DEPTH,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
}; };
use pbs_client::{ use pbs_client::{
@ -35,6 +35,7 @@ use pbs_datastore::{check_backup_owner, DataStore, StoreProgress};
use pbs_tools::sha::sha256; use pbs_tools::sha::sha256;
use proxmox_rest_server::WorkerTask; use proxmox_rest_server::WorkerTask;
use crate::backup::{check_ns_modification_privs, check_ns_privs};
use crate::tools::parallel_handler::ParallelHandler; use crate::tools::parallel_handler::ParallelHandler;
/// Parameters for a pull operation. /// Parameters for a pull operation.
@ -791,18 +792,6 @@ async fn query_namespaces(
Ok(list.iter().map(|item| item.ns.clone()).collect()) Ok(list.iter().map(|item| item.ns.clone()).collect())
} }
fn check_ns_privs(
store_with_ns: &DatastoreWithNamespace,
owner: &Authid,
privs: u64,
) -> Result<(), Error> {
let user_info = CachedUserInfo::new()?;
// TODO re-sync with API, maybe find common place?
user_info.check_privs(owner, &store_with_ns.acl_path(), privs, true)
}
fn check_and_create_ns( fn check_and_create_ns(
params: &PullParameters, params: &PullParameters,
store_with_ns: &DatastoreWithNamespace, store_with_ns: &DatastoreWithNamespace,
@ -811,32 +800,26 @@ fn check_and_create_ns(
let mut created = false; let mut created = false;
if !ns.is_root() && !params.store.namespace_path(&ns).exists() { if !ns.is_root() && !params.store.namespace_path(&ns).exists() {
let mut parent = ns.clone(); check_ns_modification_privs(&store_with_ns, &params.owner)
let name = parent.pop();
let parent = params.store_with_ns(parent);
check_ns_privs(&parent, &params.owner, PRIV_DATASTORE_MODIFY)
.map_err(|err| format_err!("Creating {ns} not allowed - {err}"))?; .map_err(|err| format_err!("Creating {ns} not allowed - {err}"))?;
if let Some(name) = name { let name = match ns.components().last() {
if let Err(err) = params.store.create_namespace(&parent.ns, name) { Some(name) => name.to_owned(),
bail!( None => {
"sync into {} failed - namespace creation failed: {}", bail!("Failed to determine last component of namespace.");
&store_with_ns,
err
);
} }
created = true; };
} else {
if let Err(err) = params.store.create_namespace(&ns.parent(), name) {
bail!( bail!(
"sync into {} failed - namespace creation failed - couldn't determine parent namespace", "sync into {} failed - namespace creation failed: {}",
&store_with_ns, &store_with_ns,
err
); );
} }
created = true;
} }
// TODO re-sync with API, maybe find common place?
check_ns_privs(&store_with_ns, &params.owner, PRIV_DATASTORE_BACKUP) check_ns_privs(&store_with_ns, &params.owner, PRIV_DATASTORE_BACKUP)
.map_err(|err| format_err!("sync into {store_with_ns} not allowed - {err}"))?; .map_err(|err| format_err!("sync into {store_with_ns} not allowed - {err}"))?;
@ -844,10 +827,10 @@ fn check_and_create_ns(
} }
fn check_and_remove_ns(params: &PullParameters, local_ns: &BackupNamespace) -> Result<bool, Error> { fn check_and_remove_ns(params: &PullParameters, local_ns: &BackupNamespace) -> Result<bool, Error> {
let parent = local_ns.clone().parent(); let store_with_ns = params.store_with_ns(local_ns.clone());
let store_with_parent = params.store_with_ns(parent); check_ns_modification_privs(&store_with_ns, &params.owner)
check_ns_privs(&store_with_parent, &params.owner, PRIV_DATASTORE_MODIFY)
.map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?; .map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?;
params.store.remove_namespace_recursive(local_ns, true) params.store.remove_namespace_recursive(local_ns, true)
} }