api: datastore: make permission checks namespace aware

We probably can combine the base permission + owner check, but for
now add explicit ones to upfront so that the change is simpler as
only one thing is done.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-05-05 19:07:28 +02:00
parent 18934ae56b
commit 7d6fc15b20

View File

@ -80,7 +80,12 @@ fn check_priv_or_backup_owner(
required_privs: u64,
) -> Result<(), Error> {
let user_info = CachedUserInfo::new()?;
let privs = user_info.lookup_privs(auth_id, &["datastore", store.name()]);
let privs = if group.ns.is_root() {
user_info.lookup_privs(auth_id, &["datastore", store.name()])
} else {
user_info.lookup_privs(auth_id, &["datastore", store.name(), &group.ns.to_string()])
};
if privs & required_privs == 0 {
let owner = store.get_owner(group)?;
@ -89,6 +94,33 @@ fn check_priv_or_backup_owner(
Ok(())
}
// TODO: move somewhere we can reuse it from (namespace has its own copy atm.)
fn get_ns_privs(store: &str, ns: &BackupNamespace, auth_id: &Authid) -> Result<u64, Error> {
let user_info = CachedUserInfo::new()?;
Ok(if ns.is_root() {
user_info.lookup_privs(auth_id, &["datastore", store])
} else {
user_info.lookup_privs(auth_id, &["datastore", store, &ns.to_string()])
})
}
// for asserting a base priv existence but also returning the prics for more fine grained checking
// locally
fn get_ns_privs_checked(
store: &str,
ns: &BackupNamespace,
auth_id: &Authid,
required_privs: u64,
) -> Result<u64, Error> {
let privs = get_ns_privs(store, ns, auth_id)?;
if privs & required_privs == 0 {
proxmox_router::http_bail!(FORBIDDEN, "permission check failed");
}
Ok(privs)
}
fn read_backup_index(
store: &DataStore,
backup_dir: &BackupDir,
@ -155,10 +187,9 @@ fn get_all_snapshot_files(
},
returns: pbs_api_types::ADMIN_DATASTORE_LIST_GROUPS_RETURN_TYPE,
access: {
permission: &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires DATASTORE_AUDIT for all or DATASTORE_BACKUP for owned groups on \
/datastore/{store}[/{namespace}]",
},
)]
/// List backup groups.
@ -168,14 +199,20 @@ pub fn list_groups(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<GroupListItem>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
let backup_ns = backup_ns.unwrap_or_default();
let user_privs = get_ns_privs_checked(
&store,
&backup_ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
datastore
.iter_backup_groups(backup_ns.unwrap_or_default())? // FIXME: Namespaces and recursion parameters!
.iter_backup_groups(backup_ns)? // FIXME: Namespaces and recursion parameters!
.try_fold(Vec::new(), |mut group_info, group| {
let group = group?;
let owner = match datastore.get_owner(group.as_ref()) {
@ -238,10 +275,9 @@ pub fn list_groups(
},
},
access: {
permission: &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_MODIFY| PRIV_DATASTORE_PRUNE,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any\
or DATASTORE_PRUNE and being the owner of the group",
},
)]
/// Delete backup group including all snapshots.
@ -253,6 +289,13 @@ pub fn delete_group(
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
check_priv_or_backup_owner(&datastore, &group, &auth_id, PRIV_DATASTORE_MODIFY)?;
@ -276,10 +319,9 @@ pub fn delete_group(
},
returns: pbs_api_types::ADMIN_DATASTORE_LIST_SNAPSHOT_FILES_RETURN_TYPE,
access: {
permission: &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT or \
DATASTORE_READ for any or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// List snapshot files.
@ -290,6 +332,14 @@ pub fn list_snapshot_files(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<BackupContent>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let snapshot = datastore.backup_dir(backup_dir)?;
@ -319,10 +369,9 @@ pub fn list_snapshot_files(
},
},
access: {
permission: &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_MODIFY| PRIV_DATASTORE_PRUNE,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any\
or DATASTORE_PRUNE and being the owner of the group",
},
)]
/// Delete backup snapshot.
@ -334,6 +383,13 @@ pub fn delete_snapshot(
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let snapshot = datastore.backup_dir(backup_dir)?;
@ -370,10 +426,9 @@ pub fn delete_snapshot(
},
returns: pbs_api_types::ADMIN_DATASTORE_LIST_SNAPSHOTS_RETURN_TYPE,
access: {
permission: &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// List backup snapshots.
@ -387,15 +442,20 @@ pub fn list_snapshots(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<SnapshotListItem>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
let backup_ns = backup_ns.unwrap_or_default();
let user_privs = get_ns_privs_checked(
&store,
&backup_ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
)?;
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let backup_ns = backup_ns.unwrap_or_default();
// 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
let groups = match (backup_type, backup_id) {
@ -575,7 +635,8 @@ fn get_snapshots_count(
type: DataStoreStatus,
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Privilege(
&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
},
)]
/// Get datastore status.
@ -651,7 +712,9 @@ pub fn status(
schema: UPID_SCHEMA,
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_VERIFY | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_VERIFY for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Verify backups.
@ -668,10 +731,17 @@ pub fn verify(
outdated_after: Option<i64>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_ns = backup_ns.unwrap_or_default();
get_ns_privs_checked(
&store,
&backup_ns,
&auth_id,
PRIV_DATASTORE_VERIFY | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let ignore_verified = ignore_verified.unwrap_or(true);
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let worker_id;
let mut backup_dir = None;
@ -680,8 +750,6 @@ pub fn verify(
// FIXME: Recursion
// FIXME: Namespaces and worker ID, could this be an issue?
let backup_ns = backup_ns.unwrap_or_default();
match (backup_type, backup_id, backup_time) {
(Some(backup_type), Some(backup_id), Some(backup_time)) => {
worker_id = format!(
@ -804,7 +872,9 @@ pub fn verify(
},
returns: pbs_api_types::ADMIN_DATASTORE_PRUNE_RETURN_TYPE,
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE, true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any\
or DATASTORE_PRUNE and being the owner of the group",
},
)]
/// Prune a group on the datastore
@ -817,6 +887,12 @@ pub fn prune(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
@ -937,7 +1013,8 @@ pub fn prune(
schema: UPID_SCHEMA,
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE, true),
permission: &Permission::Privilege(
&["datastore", "{store}"], PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_PRUNE, true),
},
)]
/// Prune the datastore
@ -954,6 +1031,8 @@ pub fn prune_datastore(
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
// FIXME: also allow a per-namespace pruning with max-depth
let upid_str = WorkerTask::new_thread(
"prune",
Some(store.clone()),
@ -1044,6 +1123,24 @@ pub fn garbage_collection_status(
Ok(status)
}
fn can_access_any_ns(store: Arc<DataStore>, auth_id: &Authid, user_info: &CachedUserInfo) -> bool {
// NOTE: traversing the datastore could be avoided if we had an "ACL tree: is there any priv
// below /datastore/{store}" helper
let mut iter =
if let Ok(iter) = store.recursive_iter_backup_ns_ok(BackupNamespace::root(), None) {
iter
} else {
return false;
};
let wanted =
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP;
let name = store.name();
iter.any(|ns| -> bool {
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", name, &ns.to_string()]);
user_privs & wanted != 0
})
}
#[api(
returns: {
description: "List the accessible datastores.",
@ -1070,10 +1167,24 @@ pub fn get_datastore_list(
for (store, (_, data)) in &config.sections {
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", store]);
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP)) != 0;
if allowed {
let mut allow_id = false;
if !allowed {
let scfg: pbs_api_types::DataStoreConfig = serde_json::from_value(data.to_owned())?;
// safety: we just cannot go through lookup as we must avoid an operation check
if let Ok(datastore) = unsafe { DataStore::open_from_config(scfg, None) } {
allow_id = can_access_any_ns(datastore, &auth_id, &user_info);
}
}
if allowed || allow_id {
list.push(DataStoreListItem {
store: store.clone(),
comment: data["comment"].as_str().map(String::from),
comment: if !allowed {
None
} else {
data["comment"].as_str().map(String::from)
},
maintenance: data["maintenance-mode"].as_str().map(String::from),
});
}
@ -1098,12 +1209,11 @@ pub const API_METHOD_DOWNLOAD_FILE: ApiMethod = ApiMethod::new(
),
)
.access(
None,
&Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
true,
Some(
"Requires on /datastore/{store}[/{namespace}] either DATASTORE_READ for any or \
DATASTORE_BACKUP and being the owner of the group",
),
&Permission::Anybody,
);
pub fn download_file(
@ -1114,15 +1224,20 @@ pub fn download_file(
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let store = required_string_param(&param, "store")?;
let datastore = DataStore::lookup_datastore(store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?;
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(store, Some(Operation::Read))?;
let backup_dir = datastore.backup_dir(backup_dir)?;
let file_name = required_string_param(&param, "file-name")?.to_owned();
let backup_dir = datastore.backup_dir(Deserialize::deserialize(&param)?)?;
check_priv_or_backup_owner(
&datastore,
backup_dir.as_ref(),
@ -1178,12 +1293,11 @@ pub const API_METHOD_DOWNLOAD_FILE_DECODED: ApiMethod = ApiMethod::new(
),
)
.access(
None,
&Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
true,
Some(
"Requires on /datastore/{store}[/{namespace}] either DATASTORE_READ for any or \
DATASTORE_BACKUP and being the owner of the group",
),
&Permission::Anybody,
);
pub fn download_file_decoded(
@ -1194,15 +1308,20 @@ pub fn download_file_decoded(
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let store = required_string_param(&param, "store")?;
let datastore = DataStore::lookup_datastore(store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?;
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(store, Some(Operation::Read))?;
let backup_dir = datastore.backup_dir(backup_dir)?;
let file_name = required_string_param(&param, "file-name")?.to_owned();
let backup_dir = datastore.backup_dir(Deserialize::deserialize(&param)?)?;
check_priv_or_backup_owner(
&datastore,
backup_dir.as_ref(),
@ -1308,7 +1427,7 @@ pub const API_METHOD_UPLOAD_BACKUP_LOG: ApiMethod = ApiMethod::new(
)
.access(
Some("Only the backup creator/owner is allowed to do this."),
&Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_BACKUP, false),
&Permission::Anybody,
);
pub fn upload_backup_log(
@ -1319,14 +1438,20 @@ pub fn upload_backup_log(
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?;
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(store, Some(Operation::Write))?;
let backup_dir = datastore.backup_dir(backup_dir)?;
let file_name = CLIENT_LOG_BLOB_NAME;
let backup_dir = datastore.backup_dir(Deserialize::deserialize(&param)?)?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let owner = datastore.get_owner(backup_dir.as_ref())?;
check_backup_owner(&owner, &auth_id)?;
@ -1374,7 +1499,9 @@ pub fn upload_backup_log(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP, true),
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_READ for any or \
DATASTORE_BACKUP and being the owner of the group",
permission: &Permission::Anybody,
},
)]
/// Get the entries of the given path of the catalog
@ -1384,10 +1511,15 @@ pub fn catalog(
filepath: String,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<ArchiveEntry>, Error> {
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let backup_dir = datastore.backup_dir(backup_dir)?;
check_priv_or_backup_owner(
@ -1445,10 +1577,12 @@ pub const API_METHOD_PXAR_FILE_DOWNLOAD: ApiMethod = ApiMethod::new(
("tar", true, &BooleanSchema::new("Download as .tar.zst").schema()),
]),
)
).access(None, &Permission::Privilege(
&["datastore", "{store}"],
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
true)
).access(
Some(
"Requires on /datastore/{store}[/{namespace}] either DATASTORE_READ for any or \
DATASTORE_BACKUP and being the owner of the group",
),
&Permission::Anybody,
);
pub fn pxar_file_download(
@ -1459,17 +1593,22 @@ pub fn pxar_file_download(
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let store = required_string_param(&param, "store")?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?;
let backup_dir: pbs_api_types::BackupDir = Deserialize::deserialize(&param)?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let backup_dir = datastore.backup_dir(backup_dir)?;
let filepath = required_string_param(&param, "filepath")?.to_owned();
let tar = param["tar"].as_bool().unwrap_or(false);
let backup_dir = datastore.backup_dir(Deserialize::deserialize(&param)?)?;
check_priv_or_backup_owner(
&datastore,
backup_dir.as_ref(),
@ -1585,7 +1724,8 @@ pub fn pxar_file_download(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Privilege(
&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
},
)]
/// Read datastore stats
@ -1648,7 +1788,9 @@ pub fn get_active_operations(store: String, _param: Value) -> Result<Value, Erro
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Get "notes" for a backup group
@ -1657,9 +1799,14 @@ pub fn get_group_notes(
backup_group: pbs_api_types::BackupGroup,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_group.ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
check_priv_or_backup_owner(&datastore, &backup_group, &auth_id, PRIV_DATASTORE_AUDIT)?;
@ -1681,9 +1828,9 @@ pub fn get_group_notes(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"],
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Set "notes" for a backup group
@ -1693,9 +1840,14 @@ pub fn set_group_notes(
notes: String,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
check_priv_or_backup_owner(&datastore, &backup_group, &auth_id, PRIV_DATASTORE_MODIFY)?;
@ -1716,7 +1868,9 @@ pub fn set_group_notes(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Get "notes" for a specific backup
@ -1725,9 +1879,15 @@ pub fn get_notes(
backup_dir: pbs_api_types::BackupDir,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = datastore.backup_dir(backup_dir)?;
check_priv_or_backup_owner(
@ -1758,9 +1918,9 @@ pub fn get_notes(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"],
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Set "notes" for a specific backup
@ -1770,9 +1930,15 @@ pub fn set_notes(
notes: String,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = datastore.backup_dir(backup_dir)?;
check_priv_or_backup_owner(
@ -1802,7 +1968,9 @@ pub fn set_notes(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_AUDIT for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// Query protection for a specific backup
@ -1811,9 +1979,15 @@ pub fn get_protection(
backup_dir: pbs_api_types::BackupDir,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<bool, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = datastore.backup_dir(backup_dir)?;
check_priv_or_backup_owner(
@ -1840,9 +2014,9 @@ pub fn get_protection(
},
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"],
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
true),
permission: &Permission::Anybody,
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
or DATASTORE_BACKUP and being the owner of the group",
},
)]
/// En- or disable protection for a specific backup
@ -1852,9 +2026,15 @@ pub fn set_protection(
protected: bool,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
get_ns_privs_checked(
&store,
&backup_dir.group.ns,
&auth_id,
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = datastore.backup_dir(backup_dir)?;
check_priv_or_backup_owner(
@ -1882,7 +2062,8 @@ pub fn set_protection(
},
access: {
permission: &Permission::Anybody,
description: "Datastore.Modify on whole datastore, or changing ownership between user and a user's token for owned backups with Datastore.Backup"
description: "Datastore.Modify on whole datastore, or changing ownership between user and \
a user's token for owned backups with Datastore.Backup"
},
)]
/// Change owner of a backup group
@ -1894,17 +2075,12 @@ pub fn set_backup_owner(
) -> Result<(), Error> {
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let privs = get_ns_privs(&store, &backup_group.ns, &auth_id)?;
let backup_group = datastore.backup_group(backup_group);
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]);
let allowed = if (privs & PRIV_DATASTORE_MODIFY) != 0 {
// High-privilege user/token
true
true // High-privilege user/token
} else if (privs & PRIV_DATASTORE_BACKUP) != 0 {
let owner = datastore.get_owner(backup_group.as_ref())?;
@ -1942,6 +2118,8 @@ pub fn set_backup_owner(
));
}
let user_info = CachedUserInfo::new()?;
if !user_info.is_active_auth_id(&new_owner) {
bail!(
"{} '{}' is inactive or non-existent",