From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 72E9A7A8E4 for ; Tue, 5 Jul 2022 15:09:17 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A21732F50E for ; Tue, 5 Jul 2022 15:08:57 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Tue, 5 Jul 2022 15:08:49 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id C2A3943DB0 for ; Tue, 5 Jul 2022 15:08:47 +0200 (CEST) From: Hannes Laimer To: pbs-devel@lists.proxmox.com Date: Tue, 5 Jul 2022 13:08:12 +0000 Message-Id: <20220705130834.14285-7-h.laimer@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20220705130834.14285-1-h.laimer@proxmox.com> References: <20220705130834.14285-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.041 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [data.store, mod.rs, update.store] Subject: [pbs-devel] [PATCH proxmox-backup 04/26] api2: add config endpoints for RemovableDeviceConfig X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 05 Jul 2022 13:09:17 -0000 list, create, read, update and delete Signed-off-by: Hannes Laimer --- src/api2/config/mod.rs | 2 + src/api2/config/removable_device.rs | 284 ++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 src/api2/config/removable_device.rs diff --git a/src/api2/config/mod.rs b/src/api2/config/mod.rs index 265b6fc8..67b3099c 100644 --- a/src/api2/config/mod.rs +++ b/src/api2/config/mod.rs @@ -13,6 +13,7 @@ pub mod media_pool; pub mod metrics; pub mod prune; pub mod remote; +pub mod removable_device; pub mod sync; pub mod tape_backup_job; pub mod tape_encryption_keys; @@ -30,6 +31,7 @@ const SUBDIRS: SubdirMap = &sorted!([ ("metrics", &metrics::ROUTER), ("prune", &prune::ROUTER), ("remote", &remote::ROUTER), + ("removable-device", &removable_device::ROUTER), ("sync", &sync::ROUTER), ("tape-backup-job", &tape_backup_job::ROUTER), ("tape-encryption-keys", &tape_encryption_keys::ROUTER), diff --git a/src/api2/config/removable_device.rs b/src/api2/config/removable_device.rs new file mode 100644 index 00000000..c3dc5bad --- /dev/null +++ b/src/api2/config/removable_device.rs @@ -0,0 +1,284 @@ +use anyhow::Error; +use hex::FromHex; +use pbs_api_types::{ + Authid, DataStoreConfig, RemovableDeviceConfig, RemovableDeviceConfigUpdater, + DEVICE_NAME_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, +}; +use pbs_config::{datastore, removable_device, CachedUserInfo}; +use proxmox_router::{http_bail, Permission, Router, RpcEnvironment}; +use proxmox_schema::{api, param_bail}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +fn check_store(store: &str) -> Result<(), Error> { + let (datastore_section_config, _digest) = datastore::config()?; + match datastore_section_config.lookup::("datastore", store) { + Ok(store) if store.removable => Ok(()), + Ok(_) => param_bail!("store", "datastore '{}' is not marked as removable.", store), + Err(_) => param_bail!("store", "datastore '{}' does not exist.", store), + } +} + +#[api( + input: { + properties: {}, + }, + returns: { + description: "List configured removable devices.", + type: Array, + items: { type: RemovableDeviceConfig }, + }, + access: { + permission: &Permission::Anybody, + description: "Requires Datastore.Audit.", + }, +)] +/// List all removable devices. +pub fn list_removable_device( + _param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result, Error> { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_MODIFY; + + let (config, digest) = removable_device::config()?; + + let list = config + .convert_to_typed_array("removable-device")? + .into_iter() + .filter(|device: &RemovableDeviceConfig| { + let privs = user_info.lookup_privs(&auth_id, &device.acl_path()); + privs & required_privs != 00 + }) + .collect(); + + rpcenv["digest"] = hex::encode(&digest).into(); + + Ok(list) +} + +#[api( + protected: true, + input: { + properties: { + config: { + type: RemovableDeviceConfig, + flatten: true, + }, + }, + }, + access: { + permission: &Permission::Anybody, + description: "Requires Datastore.Modify on removable devices's datastore.", + }, +)] +/// Create a new removable device. +pub fn create_removable_device( + config: RemovableDeviceConfig, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + user_info.check_privs(&auth_id, &config.acl_path(), PRIV_DATASTORE_MODIFY, true)?; + + let _lock = removable_device::lock_config()?; + + let (mut section_config, _digest) = removable_device::config()?; + if section_config.sections.get(&config.name).is_some() { + param_bail!("name", "device '{}' already exists.", config.name); + } + if section_config + .convert_to_typed_array::("removable-device")? + .iter() + .any(|device| device.uuid.eq(&config.uuid)) + { + param_bail!("uuid", "device with uuid '{}' already exists.", config.uuid); + } + + check_store(&config.store)?; + + section_config.set_data(&config.name, "removable-device", &config)?; + + removable_device::save_config(§ion_config)?; + + Ok(()) +} + +#[api] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Deletable property name +pub enum DeletableProperty {} + +#[api( + protected: true, + input: { + properties: { + name: { + schema: DEVICE_NAME_SCHEMA, + }, + update: { + type: RemovableDeviceConfigUpdater, + flatten: true, + }, + delete: { + description: "List of properties to delete.", + type: Array, + optional: true, + items: { + type: DeletableProperty, + } + }, + digest: { + optional: true, + schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + }, + }, + }, + access: { + permission: &Permission::Anybody, + description: "Requires Datastore.Modify on removable devices's datastore.", + }, +)] +/// Update removable device config. +pub fn update_removable_device( + name: String, + update: RemovableDeviceConfigUpdater, + delete: Option>, + digest: Option, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + let _lock = removable_device::lock_config()?; + + // pass/compare digest + let (mut config, expected_digest) = removable_device::config()?; + + if let Some(ref digest) = digest { + let digest = <[u8; 32]>::from_hex(digest)?; + crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; + } + + let mut data: RemovableDeviceConfig = config.lookup("removable-device", &name)?; + + user_info.check_privs(&auth_id, &data.acl_path(), PRIV_DATASTORE_MODIFY, true)?; + + if let Some(_delete) = delete {} + + if let Some(initialized) = update.initialized { + data.initialized = initialized; + } + + if let Some(store) = update.store { + check_store(&store)?; + data.store = store; + } + + config.set_data(&name, "removable-device", &data)?; + + removable_device::save_config(&config)?; + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: DEVICE_NAME_SCHEMA, + }, + }, + }, + returns: { type: RemovableDeviceConfig }, + access: { + permission: &Permission::Anybody, + description: "Requires Datastore.Audit removable devices's datastore.", + }, +)] +/// Read a removable device configuration. +pub fn read_removable_device( + name: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + let (config, digest) = removable_device::config()?; + + let device_config: RemovableDeviceConfig = config.lookup("removable-device", &name)?; + + user_info.check_privs( + &auth_id, + &device_config.acl_path(), + PRIV_DATASTORE_AUDIT, + true, + )?; + + rpcenv["digest"] = hex::encode(&digest).into(); + + Ok(device_config) +} + +#[api( + protected: true, + input: { + properties: { + name: { + schema: DEVICE_NAME_SCHEMA, + }, + digest: { + optional: true, + schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + }, + }, + }, + access: { + permission: &Permission::Anybody, + description: "Requires Datastore.Modify on removable devices's datastore.", + }, +)] +/// Remove a removable device configuration +pub fn delete_removable_device( + name: String, + digest: Option, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + let user_info = CachedUserInfo::new()?; + + let _lock = removable_device::lock_config()?; + + let (mut config, expected_digest) = removable_device::config()?; + + let device: RemovableDeviceConfig = config.lookup("removable-device", &name)?; + + user_info.check_privs(&auth_id, &device.acl_path(), PRIV_DATASTORE_MODIFY, true)?; + + if let Some(ref digest) = digest { + let digest = <[u8; 32]>::from_hex(digest)?; + crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; + } + + if config.sections.remove(&name).is_none() { + http_bail!(NOT_FOUND, "removable device '{}' does not exist.", name); + } + + removable_device::save_config(&config)?; + + Ok(()) +} + +const ITEM_ROUTER: Router = Router::new() + .get(&API_METHOD_READ_REMOVABLE_DEVICE) + .put(&API_METHOD_UPDATE_REMOVABLE_DEVICE) + .delete(&API_METHOD_DELETE_REMOVABLE_DEVICE); + +pub const ROUTER: Router = Router::new() + .get(&API_METHOD_LIST_REMOVABLE_DEVICE) + .post(&API_METHOD_CREATE_REMOVABLE_DEVICE) + .match_all("name", &ITEM_ROUTER); -- 2.30.2