PRIV_DATASTORE_READ,
PRIV_DATASTORE_PRUNE,
PRIV_DATASTORE_BACKUP,
+ PRIV_DATASTORE_VERIFY,
};
-fn check_backup_owner(
+fn check_priv_or_backup_owner(
store: &DataStore,
group: &BackupGroup,
- userid: &Userid,
+ auth_id: &Authid,
+ required_privs: u64,
+) -> Result<(), Error> {
+ let user_info = CachedUserInfo::new()?;
+ let privs = user_info.lookup_privs(&auth_id, &["datastore", store.name()]);
+
+ if privs & required_privs == 0 {
+ let owner = store.get_owner(group)?;
+ check_backup_owner(&owner, auth_id)?;
+ }
+ Ok(())
+}
+
+fn check_backup_owner(
+ owner: &Authid,
+ auth_id: &Authid,
) -> Result<(), Error> {
- let owner = store.get_owner(group)?;
- if &owner != userid {
- bail!("backup owner check failed ({} != {})", userid, owner);
+ let correct_owner = owner == auth_id
+ || (owner.is_token() && &Authid::from(owner.user().clone()) == auth_id);
+ if !correct_owner {
+ bail!("backup owner check failed ({} != {})", auth_id, owner);
}
Ok(())
}
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<GroupListItem>, Error> {
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
let datastore = DataStore::lookup_datastore(&store)?;
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
let owner = datastore.get_owner(group)?;
- if !list_all && owner != userid {
+ if !list_all && check_backup_owner(&owner, &auth_id).is_err() {
continue;
}
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<BackupContent>, Error> {
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
-
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let datastore = DataStore::lookup_datastore(&store)?;
let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ)) != 0;
- if !allowed { check_backup_owner(&datastore, snapshot.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, snapshot.group(), &auth_id, PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_READ)?;
let info = BackupInfo::new(&datastore.base_path(), snapshot)?;
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
-
let datastore = DataStore::lookup_datastore(&store)?;
- let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
- if !allowed { check_backup_owner(&datastore, snapshot.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, snapshot.group(), &auth_id, PRIV_DATASTORE_MODIFY)?;
datastore.remove_backup_dir(&snapshot, false)?;
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<SnapshotListItem>, Error> {
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
let datastore = DataStore::lookup_datastore(&store)?;
let list_all = (user_privs & PRIV_DATASTORE_AUDIT) != 0;
let owner = datastore.get_owner(group)?;
- if !list_all && owner != userid {
+ if !list_all && check_backup_owner(&owner, &auth_id).is_err() {
continue;
}
Ok(snapshots)
}
-// returns a map from type to (group_count, snapshot_count)
-fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize)>, Error> {
+fn get_snapshots_count(store: &DataStore) -> Result<Counts, Error> {
let base_path = store.base_path();
let backup_list = BackupInfo::list_backups(&base_path)?;
let mut groups = HashSet::new();
- let mut result: HashMap<String, (usize, usize)> = HashMap::new();
+
+ let mut result = Counts {
+ ct: None,
+ host: None,
+ vm: None,
+ other: None,
+ };
+
for info in backup_list {
let group = info.backup_dir.group();
new_id = true;
}
- if let Some(mut counts) = result.get_mut(backup_type) {
- counts.1 += 1;
- if new_id {
- counts.0 +=1;
- }
- } else {
- result.insert(backup_type.to_string(), (1, 1));
+ let mut counts = match backup_type {
+ "ct" => result.ct.take().unwrap_or(Default::default()),
+ "host" => result.host.take().unwrap_or(Default::default()),
+ "vm" => result.vm.take().unwrap_or(Default::default()),
+ _ => result.other.take().unwrap_or(Default::default()),
+ };
+
+ counts.snapshots += 1;
+ if new_id {
+ counts.groups +=1;
+ }
+
+ match backup_type {
+ "ct" => result.ct = Some(counts),
+ "host" => result.host = Some(counts),
+ "vm" => result.vm = Some(counts),
+ _ => result.other = Some(counts),
}
}
},
},
returns: {
- description: "The overall Datastore status and information.",
- type: Object,
- properties: {
- storage: {
- type: StorageStatus,
- },
- counts: {
- description: "Group and Snapshot counts per Type",
- type: Object,
- properties: { },
- },
- "gc-status": {
- type: GarbageCollectionStatus,
- },
- },
+ type: DataStoreStatus,
},
access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
store: String,
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Value, Error> {
+) -> Result<DataStoreStatus, Error> {
let datastore = DataStore::lookup_datastore(&store)?;
- let storage_status = crate::tools::disks::disk_usage(&datastore.base_path())?;
- let counts = get_snaphots_count(&datastore)?;
+ let storage = crate::tools::disks::disk_usage(&datastore.base_path())?;
+ let counts = get_snapshots_count(&datastore)?;
let gc_status = datastore.last_gc_status();
- let res = json!({
- "storage": storage_status,
- "counts": counts,
- "gc-status": gc_status,
- });
-
- Ok(res)
+ Ok(DataStoreStatus {
+ total: storage.total,
+ used: storage.used,
+ avail: storage.avail,
+ gc_status,
+ counts,
+ })
}
#[api(
schema: UPID_SCHEMA,
},
access: {
- permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP, true), // fixme
+ permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_VERIFY | PRIV_DATASTORE_BACKUP, true),
},
)]
/// Verify backups.
) -> Result<Value, Error> {
let datastore = DataStore::lookup_datastore(&store)?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let worker_id;
let mut backup_dir = None;
(Some(backup_type), Some(backup_id), Some(backup_time)) => {
worker_id = format!("{}:{}/{}/{:08X}", store, backup_type, backup_id, backup_time);
let dir = BackupDir::new(backup_type, backup_id, backup_time)?;
+
+ check_priv_or_backup_owner(&datastore, dir.group(), &auth_id, PRIV_DATASTORE_VERIFY)?;
+
backup_dir = Some(dir);
worker_type = "verify_snapshot";
}
(Some(backup_type), Some(backup_id), None) => {
worker_id = format!("{}:{}/{}", store, backup_type, backup_id);
let group = BackupGroup::new(backup_type, backup_id);
+
+ check_priv_or_backup_owner(&datastore, &group, &auth_id, PRIV_DATASTORE_VERIFY)?;
+
backup_group = Some(group);
worker_type = "verify_group";
}
_ => bail!("parameters do not specify a backup group or snapshot"),
}
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false };
let upid_str = WorkerTask::new_thread(
worker_type,
Some(worker_id.clone()),
- userid,
+ auth_id.clone(),
to_stdout,
move |worker| {
let verified_chunks = Arc::new(Mutex::new(HashSet::with_capacity(1024*16)));
corrupt_chunks,
worker.clone(),
worker.upid().clone(),
+ None,
)? {
res.push(backup_dir.to_string());
}
None,
worker.clone(),
worker.upid(),
+ None,
)?;
failed_dirs
} else {
- verify_all_backups(datastore, worker.clone(), worker.upid())?
+ let privs = CachedUserInfo::new()?
+ .lookup_privs(&auth_id, &["datastore", &store]);
+
+ let owner = if privs & PRIV_DATASTORE_VERIFY == 0 {
+ Some(auth_id)
+ } else {
+ None
+ };
+
+ verify_all_backups(datastore, worker.clone(), worker.upid(), owner, None)?
};
if failed_dirs.len() > 0 {
worker.log("Failed to verify following snapshots:");
let backup_type = tools::required_string_param(¶m, "backup-type")?;
let backup_id = tools::required_string_param(¶m, "backup-id")?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let dry_run = param["dry-run"].as_bool().unwrap_or(false);
let datastore = DataStore::lookup_datastore(&store)?;
- let allowed = (user_privs & PRIV_DATASTORE_MODIFY) != 0;
- if !allowed { check_backup_owner(&datastore, &group, &userid)?; }
+ check_priv_or_backup_owner(&datastore, &group, &auth_id, PRIV_DATASTORE_MODIFY)?;
let prune_options = PruneOptions {
keep_last: param["keep-last"].as_u64(),
// We use a WorkerTask just to have a task log, but run synchrounously
- let worker = WorkerTask::new("prune", Some(worker_id), Userid::root_userid().clone(), true)?;
+ let worker = WorkerTask::new("prune", Some(worker_id), auth_id.clone(), true)?;
if keep_all {
worker.log("No prune selection - keeping all files.");
) -> Result<Value, Error> {
let datastore = DataStore::lookup_datastore(&store)?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
println!("Starting garbage collection on store {}", store);
let upid_str = WorkerTask::new_thread(
"garbage_collection",
Some(store.clone()),
- Userid::root_userid().clone(),
+ auth_id.clone(),
to_stdout,
move |worker| {
worker.log(format!("starting garbage collection on store {}", store));
let (config, _digest) = datastore::config()?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let mut list = Vec::new();
for (store, (_, data)) in &config.sections {
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ 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 entry = json!({ "store": store });
let store = tools::required_string_param(¶m, "store")?;
let datastore = DataStore::lookup_datastore(store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_READ)?;
println!("Download {} from {} ({}/{})", file_name, store, backup_dir, file_name);
let store = tools::required_string_param(¶m, "store")?;
let datastore = DataStore::lookup_datastore(store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_READ)?;
let (manifest, files) = read_backup_index(&datastore, &backup_dir)?;
for file in files {
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- check_backup_owner(&datastore, backup_dir.group(), &userid)?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let owner = datastore.get_owner(backup_dir.group())?;
+ check_backup_owner(&owner, &auth_id)?;
let mut path = datastore.base_path();
path.push(backup_dir.relative_path());
) -> Result<Value, Error> {
let datastore = DataStore::lookup_datastore(&store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_READ)?;
let file_name = CATALOG_NAME;
let store = tools::required_string_param(¶m, "store")?;
let datastore = DataStore::lookup_datastore(&store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let filepath = tools::required_string_param(¶m, "filepath")?.to_owned();
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_READ)?;
let mut components = base64::decode(&filepath)?;
if components.len() > 0 && components[0] == '/' as u8 {
},
},
access: {
- permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP, true),
+ permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
},
)]
/// Get "notes" for a specific backup
) -> Result<String, Error> {
let datastore = DataStore::lookup_datastore(&store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
-
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_AUDIT)?;
let (manifest, _) = datastore.load_manifest(&backup_dir)?;
},
},
access: {
- permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_MODIFY, true),
+ permission: &Permission::Privilege(&["datastore", "{store}"],
+ PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
+ true),
},
)]
/// Set "notes" for a specific backup
) -> Result<(), Error> {
let datastore = DataStore::lookup_datastore(&store)?;
- let userid: Userid = rpcenv.get_user().unwrap().parse()?;
- let user_info = CachedUserInfo::new()?;
- let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
-
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time)?;
- let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
- if !allowed { check_backup_owner(&datastore, backup_dir.group(), &userid)?; }
+ check_priv_or_backup_owner(&datastore, backup_dir.group(), &auth_id, PRIV_DATASTORE_MODIFY)?;
datastore.update_manifest(&backup_dir,|manifest| {
manifest.unprotected["notes"] = notes.into();
schema: BACKUP_ID_SCHEMA,
},
"new-owner": {
- type: Userid,
+ type: Authid,
},
},
},
access: {
- permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_MODIFY, true),
+ 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"
},
)]
/// Change owner of a backup group
store: String,
backup_type: String,
backup_id: String,
- new_owner: Userid,
- _rpcenv: &mut dyn RpcEnvironment,
+ new_owner: Authid,
+ rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let datastore = DataStore::lookup_datastore(&store)?;
let backup_group = BackupGroup::new(backup_type, backup_id);
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+
let user_info = CachedUserInfo::new()?;
- if !user_info.is_active_user(&new_owner) {
- bail!("user '{}' is inactive or non-existent", new_owner);
+ let privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
+
+ let allowed = if (privs & PRIV_DATASTORE_MODIFY) != 0 {
+ // High-privilege user/token
+ true
+ } else if (privs & PRIV_DATASTORE_BACKUP) != 0 {
+ let owner = datastore.get_owner(&backup_group)?;
+
+ match (owner.is_token(), new_owner.is_token()) {
+ (true, true) => {
+ // API token to API token, owned by same user
+ let owner = owner.user();
+ let new_owner = new_owner.user();
+ owner == new_owner && Authid::from(owner.clone()) == auth_id
+ },
+ (true, false) => {
+ // API token to API token owner
+ Authid::from(owner.user().clone()) == auth_id
+ && new_owner == auth_id
+ },
+ (false, true) => {
+ // API token owner to API token
+ owner == auth_id
+ && Authid::from(new_owner.user().clone()) == auth_id
+ },
+ (false, false) => {
+ // User to User, not allowed for unprivileged users
+ false
+ },
+ }
+ } else {
+ false
+ };
+
+ if !allowed {
+ return Err(http_err!(UNAUTHORIZED,
+ "{} does not have permission to change owner of backup group '{}' to {}",
+ auth_id,
+ backup_group,
+ new_owner,
+ ));
+ }
+
+ if !user_info.is_active_auth_id(&new_owner) {
+ bail!("{} '{}' is inactive or non-existent",
+ if new_owner.is_token() {
+ "API token".to_string()
+ } else {
+ "user".to_string()
+ },
+ new_owner);
}
datastore.set_owner(&backup_group, &new_owner, true)?;