api: tape/restore: check and create target namespace
checks the privilegs for the target namespace. If that does not exist, try to recursively create them while checking the privileges. Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
6b61d319c5
commit
fc99c2791b
|
@ -386,10 +386,7 @@ impl DataStore {
|
||||||
parent: &BackupNamespace,
|
parent: &BackupNamespace,
|
||||||
name: String,
|
name: String,
|
||||||
) -> Result<BackupNamespace, Error> {
|
) -> Result<BackupNamespace, Error> {
|
||||||
let mut parent_path = self.base_path().to_owned();
|
if !self.namespace_exists(parent) {
|
||||||
parent_path.push(parent.path());
|
|
||||||
|
|
||||||
if !parent_path.exists() {
|
|
||||||
bail!("cannot create new namespace, parent {parent} doesn't already exists");
|
bail!("cannot create new namespace, parent {parent} doesn't already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,6 +401,13 @@ impl DataStore {
|
||||||
Ok(ns)
|
Ok(ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns if the given namespace exists on the datastore
|
||||||
|
pub fn namespace_exists(&self, ns: &BackupNamespace) -> bool {
|
||||||
|
let mut path = self.base_path().to_owned();
|
||||||
|
path.push(ns.path());
|
||||||
|
path.exists()
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove all backup groups of a single namespace level but not the namespace itself.
|
/// Remove all backup groups of a single namespace level but not the namespace itself.
|
||||||
///
|
///
|
||||||
/// Does *not* descends into child-namespaces and doesn't remoes the namespace itself either.
|
/// Does *not* descends into child-namespaces and doesn't remoes the namespace itself either.
|
||||||
|
|
|
@ -1244,6 +1244,7 @@ pub fn catalog_media(
|
||||||
let verbose = verbose.unwrap_or(false);
|
let verbose = verbose.unwrap_or(false);
|
||||||
let force = force.unwrap_or(false);
|
let force = force.unwrap_or(false);
|
||||||
let scan = scan.unwrap_or(false);
|
let scan = scan.unwrap_or(false);
|
||||||
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
|
|
||||||
let upid_str = run_drive_worker(
|
let upid_str = run_drive_worker(
|
||||||
rpcenv,
|
rpcenv,
|
||||||
|
@ -1340,6 +1341,7 @@ pub fn catalog_media(
|
||||||
None,
|
None,
|
||||||
&mut checked_chunks,
|
&mut checked_chunks,
|
||||||
verbose,
|
verbose,
|
||||||
|
&auth_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -200,16 +200,25 @@ impl DataStoreMap {
|
||||||
fn check_datastore_privs(
|
fn check_datastore_privs(
|
||||||
user_info: &CachedUserInfo,
|
user_info: &CachedUserInfo,
|
||||||
store: &str,
|
store: &str,
|
||||||
|
ns: &BackupNamespace,
|
||||||
auth_id: &Authid,
|
auth_id: &Authid,
|
||||||
owner: &Option<Authid>,
|
owner: Option<&Authid>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let privs = user_info.lookup_privs(auth_id, &["datastore", store]);
|
let privs = if ns.is_root() {
|
||||||
|
user_info.lookup_privs(auth_id, &["datastore", store])
|
||||||
|
} else {
|
||||||
|
user_info.lookup_privs(auth_id, &["datastore", store, &ns.to_string()])
|
||||||
|
};
|
||||||
if (privs & PRIV_DATASTORE_BACKUP) == 0 {
|
if (privs & PRIV_DATASTORE_BACKUP) == 0 {
|
||||||
bail!("no permissions on /datastore/{}", store);
|
if ns.is_root() {
|
||||||
|
bail!("no permissions on /datastore/{}", store);
|
||||||
|
} else {
|
||||||
|
bail!("no permissions on /datastore/{}/{}", store, &ns.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref owner) = owner {
|
if let Some(ref owner) = owner {
|
||||||
let correct_owner = owner == auth_id
|
let correct_owner = *owner == auth_id
|
||||||
|| (owner.is_token() && !auth_id.is_token() && owner.user() == auth_id.user());
|
|| (owner.is_token() && !auth_id.is_token() && owner.user() == auth_id.user());
|
||||||
|
|
||||||
// same permission as changing ownership after syncing
|
// same permission as changing ownership after syncing
|
||||||
|
@ -221,6 +230,43 @@ fn check_datastore_privs(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_and_create_namespaces(
|
||||||
|
user_info: &CachedUserInfo,
|
||||||
|
store: &Arc<DataStore>,
|
||||||
|
ns: &BackupNamespace,
|
||||||
|
auth_id: &Authid,
|
||||||
|
owner: Option<&Authid>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// check normal restore privs first
|
||||||
|
check_datastore_privs(user_info, store.name(), ns, auth_id, owner)?;
|
||||||
|
|
||||||
|
// try create recursively if it does not exist
|
||||||
|
if !store.namespace_exists(ns) {
|
||||||
|
let mut tmp_ns: BackupNamespace = Default::default();
|
||||||
|
let has_datastore_priv = user_info.lookup_privs(auth_id, &["datastore", store.name()])
|
||||||
|
& PRIV_DATASTORE_MODIFY
|
||||||
|
!= 0;
|
||||||
|
|
||||||
|
for comp in ns.components() {
|
||||||
|
tmp_ns.push(comp.to_string())?;
|
||||||
|
if !store.namespace_exists(&tmp_ns) {
|
||||||
|
if has_datastore_priv
|
||||||
|
|| user_info.lookup_privs(
|
||||||
|
auth_id,
|
||||||
|
&["datastore", store.name(), &tmp_ns.parent().to_string()],
|
||||||
|
) & PRIV_DATASTORE_MODIFY
|
||||||
|
!= 0
|
||||||
|
{
|
||||||
|
store.create_namespace(&tmp_ns.parent(), comp.to_string())?;
|
||||||
|
} else {
|
||||||
|
bail!("no permissions to create '{}'", tmp_ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub const ROUTER: Router = Router::new().post(&API_METHOD_RESTORE);
|
pub const ROUTER: Router = Router::new().post(&API_METHOD_RESTORE);
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -260,11 +306,12 @@ pub const ROUTER: Router = Router::new().post(&API_METHOD_RESTORE);
|
||||||
access: {
|
access: {
|
||||||
// Note: parameters are no uri parameter, so we need to test inside function body
|
// 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} \
|
description: "The user needs Tape.Read privilege on /tape/pool/{pool} \
|
||||||
and /tape/drive/{drive}, Datastore.Backup privilege on /datastore/{store}.",
|
and /tape/drive/{drive}, Datastore.Backup privilege on /datastore/{store}/[{namespace}],\
|
||||||
|
Datastore.Modify privileges to create namespaces (if they don't exist).",
|
||||||
permission: &Permission::Anybody,
|
permission: &Permission::Anybody,
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Restore data from media-set
|
/// Restore data from media-set. Namespaces will be automatically created if necessary.
|
||||||
pub fn restore(
|
pub fn restore(
|
||||||
store: String,
|
store: String,
|
||||||
drive: String,
|
drive: String,
|
||||||
|
@ -284,8 +331,20 @@ pub fn restore(
|
||||||
bail!("no datastores given");
|
bail!("no datastores given");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, (target, _)) in used_datastores.iter() {
|
for (_, (target, namespaces)) in used_datastores.iter() {
|
||||||
check_datastore_privs(&user_info, target.name(), &auth_id, &owner)?;
|
check_datastore_privs(
|
||||||
|
&user_info,
|
||||||
|
target.name(),
|
||||||
|
&Default::default(),
|
||||||
|
&auth_id,
|
||||||
|
owner.as_ref(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(namespaces) = namespaces {
|
||||||
|
for ns in namespaces {
|
||||||
|
check_and_create_namespaces(&user_info, target, ns, &auth_id, owner.as_ref())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "drive", &drive]);
|
let privs = user_info.lookup_privs(&auth_id, &["tape", "drive", &drive]);
|
||||||
|
@ -352,6 +411,8 @@ pub fn restore(
|
||||||
store_map,
|
store_map,
|
||||||
restore_owner,
|
restore_owner,
|
||||||
email,
|
email,
|
||||||
|
user_info,
|
||||||
|
&auth_id,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
restore_full_worker(
|
restore_full_worker(
|
||||||
|
@ -363,6 +424,7 @@ pub fn restore(
|
||||||
store_map,
|
store_map,
|
||||||
restore_owner,
|
restore_owner,
|
||||||
email,
|
email,
|
||||||
|
&auth_id,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -390,6 +452,7 @@ fn restore_full_worker(
|
||||||
store_map: DataStoreMap,
|
store_map: DataStoreMap,
|
||||||
restore_owner: &Authid,
|
restore_owner: &Authid,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
auth_id: &Authid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let members = inventory.compute_media_set_members(&media_set_uuid)?;
|
let members = inventory.compute_media_set_members(&media_set_uuid)?;
|
||||||
|
|
||||||
|
@ -468,6 +531,7 @@ fn restore_full_worker(
|
||||||
&mut checked_chunks_map,
|
&mut checked_chunks_map,
|
||||||
restore_owner,
|
restore_owner,
|
||||||
&email,
|
&email,
|
||||||
|
&auth_id,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,6 +548,8 @@ fn restore_list_worker(
|
||||||
store_map: DataStoreMap,
|
store_map: DataStoreMap,
|
||||||
restore_owner: &Authid,
|
restore_owner: &Authid,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
user_info: Arc<CachedUserInfo>,
|
||||||
|
auth_id: &Authid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// FIXME: Namespace needs to come from somewhere, `snapshots` is just a snapshot string list
|
// FIXME: Namespace needs to come from somewhere, `snapshots` is just a snapshot string list
|
||||||
// here.
|
// here.
|
||||||
|
@ -520,6 +586,24 @@ fn restore_list_worker(
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// only simple check, ns creation comes later
|
||||||
|
if let Err(err) = check_datastore_privs(
|
||||||
|
&user_info,
|
||||||
|
datastore.name(),
|
||||||
|
&ns,
|
||||||
|
auth_id,
|
||||||
|
Some(restore_owner),
|
||||||
|
) {
|
||||||
|
task_warn!(
|
||||||
|
worker,
|
||||||
|
"could not restore {}:{}: '{}'",
|
||||||
|
source_datastore,
|
||||||
|
snapshot,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let (owner, _group_lock) =
|
let (owner, _group_lock) =
|
||||||
datastore.create_locked_backup_group(&ns, backup_dir.as_ref(), restore_owner)?;
|
datastore.create_locked_backup_group(&ns, backup_dir.as_ref(), restore_owner)?;
|
||||||
if restore_owner != &owner {
|
if restore_owner != &owner {
|
||||||
|
@ -686,6 +770,14 @@ fn restore_list_worker(
|
||||||
tmp_path.push(&source_datastore);
|
tmp_path.push(&source_datastore);
|
||||||
tmp_path.push(snapshot);
|
tmp_path.push(snapshot);
|
||||||
|
|
||||||
|
check_and_create_namespaces(
|
||||||
|
&user_info,
|
||||||
|
&datastore,
|
||||||
|
&ns,
|
||||||
|
auth_id,
|
||||||
|
Some(restore_owner),
|
||||||
|
)?;
|
||||||
|
|
||||||
let path = datastore.snapshot_path(&ns, &backup_dir);
|
let path = datastore.snapshot_path(&ns, &backup_dir);
|
||||||
|
|
||||||
for entry in std::fs::read_dir(tmp_path)? {
|
for entry in std::fs::read_dir(tmp_path)? {
|
||||||
|
@ -1000,6 +1092,7 @@ pub fn request_and_restore_media(
|
||||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||||
restore_owner: &Authid,
|
restore_owner: &Authid,
|
||||||
email: &Option<String>,
|
email: &Option<String>,
|
||||||
|
auth_id: &Authid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let media_set_uuid = match media_id.media_set_label {
|
let media_set_uuid = match media_id.media_set_label {
|
||||||
None => bail!("restore_media: no media set - internal error"),
|
None => bail!("restore_media: no media set - internal error"),
|
||||||
|
@ -1042,6 +1135,7 @@ pub fn request_and_restore_media(
|
||||||
Some((store_map, restore_owner)),
|
Some((store_map, restore_owner)),
|
||||||
checked_chunks_map,
|
checked_chunks_map,
|
||||||
false,
|
false,
|
||||||
|
auth_id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1055,6 +1149,7 @@ pub fn restore_media(
|
||||||
target: Option<(&DataStoreMap, &Authid)>,
|
target: Option<(&DataStoreMap, &Authid)>,
|
||||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
auth_id: &Authid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||||
let mut catalog = MediaCatalog::create_temporary_database(status_path, media_id, false)?;
|
let mut catalog = MediaCatalog::create_temporary_database(status_path, media_id, false)?;
|
||||||
|
@ -1088,6 +1183,7 @@ pub fn restore_media(
|
||||||
&mut catalog,
|
&mut catalog,
|
||||||
checked_chunks_map,
|
checked_chunks_map,
|
||||||
verbose,
|
verbose,
|
||||||
|
&auth_id,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1106,7 +1202,10 @@ fn restore_archive<'a>(
|
||||||
catalog: &mut MediaCatalog,
|
catalog: &mut MediaCatalog,
|
||||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
auth_id: &Authid,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
let header: MediaContentHeader = unsafe { reader.read_le_value()? };
|
let header: MediaContentHeader = unsafe { reader.read_le_value()? };
|
||||||
if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
|
if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
|
||||||
bail!("missing MediaContentHeader");
|
bail!("missing MediaContentHeader");
|
||||||
|
@ -1144,6 +1243,13 @@ fn restore_archive<'a>(
|
||||||
|
|
||||||
if let Some((store_map, restore_owner)) = target.as_ref() {
|
if let Some((store_map, restore_owner)) = target.as_ref() {
|
||||||
if let Some((datastore, _)) = store_map.get_targets(&datastore_name, &backup_ns) {
|
if let Some((datastore, _)) = store_map.get_targets(&datastore_name, &backup_ns) {
|
||||||
|
check_and_create_namespaces(
|
||||||
|
&user_info,
|
||||||
|
&datastore,
|
||||||
|
&backup_ns,
|
||||||
|
&auth_id,
|
||||||
|
Some(restore_owner),
|
||||||
|
)?;
|
||||||
let (owner, _group_lock) = datastore.create_locked_backup_group(
|
let (owner, _group_lock) = datastore.create_locked_backup_group(
|
||||||
&backup_ns,
|
&backup_ns,
|
||||||
backup_dir.as_ref(),
|
backup_dir.as_ref(),
|
||||||
|
|
Loading…
Reference in New Issue