move and unify namespace priv helpers
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
parent
77bd14f68a
commit
ea2e91e52f
|
@ -32,14 +32,14 @@ use pxar::accessor::aio::Accessor;
|
|||
use pxar::EntryKind;
|
||||
|
||||
use pbs_api_types::{
|
||||
print_ns_and_snapshot, privs_to_priv_names, Authid, BackupContent, BackupNamespace, BackupType,
|
||||
Counts, CryptMode, DataStoreListItem, DataStoreStatus, DatastoreWithNamespace,
|
||||
GarbageCollectionStatus, GroupListItem, Operation, PruneOptions, RRDMode, RRDTimeFrame,
|
||||
SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA,
|
||||
BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA,
|
||||
IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_VERIFY, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
|
||||
print_ns_and_snapshot, Authid, BackupContent, BackupNamespace, BackupType, Counts, CryptMode,
|
||||
DataStoreListItem, DataStoreStatus, DatastoreWithNamespace, GarbageCollectionStatus,
|
||||
GroupListItem, Operation, PruneOptions, RRDMode, RRDTimeFrame, SnapshotListItem,
|
||||
SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA,
|
||||
BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA,
|
||||
MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
|
||||
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY,
|
||||
UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
|
||||
};
|
||||
use pbs_client::pxar::{create_tar, create_zip};
|
||||
use pbs_config::CachedUserInfo;
|
||||
|
@ -63,7 +63,7 @@ use proxmox_rest_server::{formatter, WorkerTask};
|
|||
use crate::api2::backup::optional_ns_param;
|
||||
use crate::api2::node::rrd::create_value_from_rrd;
|
||||
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,
|
||||
};
|
||||
|
||||
|
@ -81,63 +81,29 @@ fn get_group_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:
|
||||
// 1. check privs on NS (full or limited access)
|
||||
// 2. load datastore
|
||||
// 3. if needed (only limited access), check owner of group
|
||||
fn check_privs_and_load_store(
|
||||
store: &str,
|
||||
ns: &BackupNamespace,
|
||||
store_with_ns: &DatastoreWithNamespace,
|
||||
auth_id: &Authid,
|
||||
full_access_privs: u64,
|
||||
partial_access_privs: u64,
|
||||
operation: Option<Operation>,
|
||||
backup_group: &pbs_api_types::BackupGroup,
|
||||
) -> 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 {
|
||||
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)?;
|
||||
}
|
||||
|
||||
|
@ -222,19 +188,19 @@ pub fn list_groups(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
let ns = ns.unwrap_or_default();
|
||||
let list_all = !check_ns_privs(
|
||||
&store,
|
||||
&ns,
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let list_all = !check_ns_privs_full(
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
)?;
|
||||
|
||||
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.to_owned(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
datastore
|
||||
.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 datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
},
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_PRUNE,
|
||||
|
@ -375,10 +343,13 @@ pub fn list_snapshot_files(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -426,9 +397,13 @@ pub fn delete_snapshot(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_PRUNE,
|
||||
|
@ -482,20 +457,19 @@ pub fn list_snapshots(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let list_all = !check_ns_privs(
|
||||
&store,
|
||||
&ns,
|
||||
let list_all = !check_ns_privs_full(
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
)?;
|
||||
|
||||
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
|
||||
// backup group and provide an error free (Err -> None) accessor
|
||||
|
@ -774,9 +748,13 @@ pub fn verify(
|
|||
) -> Result<Value, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let owner_check_required = check_ns_privs(
|
||||
&store,
|
||||
&ns,
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let owner_check_required = check_ns_privs_full(
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_VERIFY,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -947,19 +925,19 @@ pub fn prune(
|
|||
) -> Result<Value, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_PRUNE,
|
||||
Some(Operation::Write),
|
||||
&group,
|
||||
)?;
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.to_owned(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let worker_id = format!("{}:{}:{}", store, 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(¶m)?;
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&backup_ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1392,8 +1369,7 @@ pub fn download_file_decoded(
|
|||
};
|
||||
let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(¶m)?;
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&backup_ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1523,8 +1499,7 @@ pub fn upload_backup_log(
|
|||
let backup_dir_api: pbs_api_types::BackupDir = Deserialize::deserialize(¶m)?;
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&backup_ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
0,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1597,9 +1572,13 @@ pub fn catalog(
|
|||
) -> Result<Vec<ArchiveEntry>, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1676,10 +1655,13 @@ pub fn pxar_file_download(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let store = required_string_param(¶m, "store")?;
|
||||
let ns = optional_ns_param(¶m)?;
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.to_owned(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(¶m)?;
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_READ,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1883,9 +1865,13 @@ pub fn get_group_notes(
|
|||
) -> Result<String, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1930,9 +1916,12 @@ pub fn set_group_notes(
|
|||
) -> Result<(), Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -1975,9 +1964,13 @@ pub fn get_notes(
|
|||
) -> Result<String, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -2027,9 +2020,13 @@ pub fn set_notes(
|
|||
) -> Result<(), Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -2077,9 +2074,12 @@ pub fn get_protection(
|
|||
) -> Result<bool, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -2125,9 +2125,12 @@ pub fn set_protection(
|
|||
) -> Result<(), Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
let datastore = check_privs_and_load_store(
|
||||
&store,
|
||||
&ns,
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
@ -2173,9 +2176,12 @@ pub fn set_backup_owner(
|
|||
) -> Result<(), Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
let ns = ns.unwrap_or_default();
|
||||
let owner_check_required = check_ns_privs(
|
||||
&store,
|
||||
&ns,
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
store: store.clone(),
|
||||
ns: ns.clone(),
|
||||
};
|
||||
let owner_check_required = check_ns_privs_full(
|
||||
&store_with_ns,
|
||||
&auth_id,
|
||||
PRIV_DATASTORE_MODIFY,
|
||||
PRIV_DATASTORE_BACKUP,
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, Error};
|
|||
use serde_json::Value;
|
||||
|
||||
use pbs_config::CachedUserInfo;
|
||||
use proxmox_router::{http_err, ApiMethod, Permission, Router, RpcEnvironment};
|
||||
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
|
||||
use proxmox_schema::*;
|
||||
|
||||
use pbs_api_types::{
|
||||
|
@ -13,18 +13,7 @@ use pbs_api_types::{
|
|||
|
||||
use pbs_datastore::DataStore;
|
||||
|
||||
// TODO: move somewhere we can reuse it from (datastore has its own copy atm.)
|
||||
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}"))
|
||||
}
|
||||
use crate::backup::{check_ns_modification_privs, check_ns_privs};
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
|
@ -62,12 +51,15 @@ pub fn create_namespace(
|
|||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
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(),
|
||||
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))?;
|
||||
|
||||
|
@ -165,17 +157,12 @@ pub fn delete_namespace(
|
|||
_info: &ApiMethod,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> 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 parent = ns.parent(); // must have MODIFY permission on parent to allow deletion
|
||||
let store_with_parent = DatastoreWithNamespace {
|
||||
let store_with_ns = DatastoreWithNamespace {
|
||||
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))?;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ use pbs_tape::{
|
|||
};
|
||||
use proxmox_rest_server::WorkerTask;
|
||||
|
||||
use crate::backup::check_ns_modification_privs;
|
||||
use crate::{
|
||||
server::lookup_user_email,
|
||||
tape::{
|
||||
|
@ -237,25 +238,16 @@ fn check_and_create_namespaces(
|
|||
|
||||
// try create recursively if it does not exist
|
||||
if !store.namespace_exists(ns) {
|
||||
let mut tmp_ns: BackupNamespace = Default::default();
|
||||
store_with_ns.ns = Default::default();
|
||||
|
||||
for comp in ns.components() {
|
||||
tmp_ns.push(comp.to_string())?;
|
||||
if !store.namespace_exists(&tmp_ns) {
|
||||
// check parent modification privs
|
||||
user_info
|
||||
.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_with_ns.ns.push(comp.to_string())?;
|
||||
if !store.namespace_exists(&store_with_ns.ns) {
|
||||
check_ns_modification_privs(&store_with_ns, auth_id).map_err(|_err| {
|
||||
format_err!("no permission to create namespace '{}'", store_with_ns.ns)
|
||||
})?;
|
||||
|
||||
store.create_namespace(&tmp_ns.parent(), comp.to_string())?;
|
||||
|
||||
// update parent for next component
|
||||
store_with_ns.ns = tmp_ns.clone();
|
||||
store.create_namespace(&store_with_ns.ns.parent(), comp.to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,72 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::{bail, Error};
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, BackupNamespace, DatastoreWithNamespace, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
|
||||
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_READ,
|
||||
privs_to_priv_names, Authid, BackupNamespace, DatastoreWithNamespace, PRIV_DATASTORE_AUDIT,
|
||||
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_READ,
|
||||
};
|
||||
use pbs_config::CachedUserInfo;
|
||||
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,
|
||||
/// most often that will be the `BackupNamespace::root()` one.
|
||||
///
|
||||
|
|
|
@ -18,7 +18,7 @@ use proxmox_sys::task_log;
|
|||
use pbs_api_types::{
|
||||
Authid, BackupNamespace, DatastoreWithNamespace, GroupFilter, GroupListItem, NamespaceListItem,
|
||||
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::{
|
||||
|
@ -35,6 +35,7 @@ use pbs_datastore::{check_backup_owner, DataStore, StoreProgress};
|
|||
use pbs_tools::sha::sha256;
|
||||
use proxmox_rest_server::WorkerTask;
|
||||
|
||||
use crate::backup::{check_ns_modification_privs, check_ns_privs};
|
||||
use crate::tools::parallel_handler::ParallelHandler;
|
||||
|
||||
/// Parameters for a pull operation.
|
||||
|
@ -791,18 +792,6 @@ async fn query_namespaces(
|
|||
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(
|
||||
params: &PullParameters,
|
||||
store_with_ns: &DatastoreWithNamespace,
|
||||
|
@ -811,16 +800,17 @@ fn check_and_create_ns(
|
|||
let mut created = false;
|
||||
|
||||
if !ns.is_root() && !params.store.namespace_path(&ns).exists() {
|
||||
let mut parent = ns.clone();
|
||||
let name = parent.pop();
|
||||
|
||||
let parent = params.store_with_ns(parent);
|
||||
|
||||
check_ns_privs(&parent, ¶ms.owner, PRIV_DATASTORE_MODIFY)
|
||||
check_ns_modification_privs(&store_with_ns, ¶ms.owner)
|
||||
.map_err(|err| format_err!("Creating {ns} not allowed - {err}"))?;
|
||||
|
||||
if let Some(name) = name {
|
||||
if let Err(err) = params.store.create_namespace(&parent.ns, name) {
|
||||
let name = match ns.components().last() {
|
||||
Some(name) => name.to_owned(),
|
||||
None => {
|
||||
bail!("Failed to determine last component of namespace.");
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = params.store.create_namespace(&ns.parent(), name) {
|
||||
bail!(
|
||||
"sync into {} failed - namespace creation failed: {}",
|
||||
&store_with_ns,
|
||||
|
@ -828,15 +818,8 @@ fn check_and_create_ns(
|
|||
);
|
||||
}
|
||||
created = true;
|
||||
} else {
|
||||
bail!(
|
||||
"sync into {} failed - namespace creation failed - couldn't determine parent namespace",
|
||||
&store_with_ns,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO re-sync with API, maybe find common place?
|
||||
check_ns_privs(&store_with_ns, ¶ms.owner, PRIV_DATASTORE_BACKUP)
|
||||
.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> {
|
||||
let parent = local_ns.clone().parent();
|
||||
let store_with_parent = params.store_with_ns(parent);
|
||||
check_ns_privs(&store_with_parent, ¶ms.owner, PRIV_DATASTORE_MODIFY)
|
||||
let store_with_ns = params.store_with_ns(local_ns.clone());
|
||||
check_ns_modification_privs(&store_with_ns, ¶ms.owner)
|
||||
.map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?;
|
||||
|
||||
params.store.remove_namespace_recursive(local_ns, true)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue