From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id EA4CB1FF0AF for ; Fri, 06 Nov 2026 18:24:08 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 67AD3216CD; Fri, 06 Nov 2026 18:23:26 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup v2 22/43] sync: push: allow push and ns creation on Remote.DatastoreAppend Date: Fri, 6 Nov 2026 18:22:14 +0100 Message-ID: <20261106172235.890542-23-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261106172235.890542-1-c.ebner@proxmox.com> References: <20261106172235.890542-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793985777090 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.459 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: UUHFEKJQUR6VC5CB33RBD2CVA5IQKDS3 X-Message-ID-Hash: UUHFEKJQUR6VC5CB33RBD2CVA5IQKDS3 X-MailFrom: c.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: A local sync job user with Remote.DatastoreAppend permissions is intended to be able to append backups on the remote just like Remote.DatastoreBackup, but also create namespaces not present on the remote, which would otherwise require Remote.DatastoreModify. The latter would however also allow for namespace destruction, so cannot be used for this. These are push job source checks only, the user connecting to the remote as configured in the remote config must be setup on the target PBS instance accordingly (i.e. with Datastore.Audit and Datastore.Append on the target datastore and sub-namespace). Signed-off-by: Christian Ebner --- src/api2/config/sync.rs | 6 +++--- src/api2/push.rs | 8 ++++---- src/server/push.rs | 13 ++++++++----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs index a9a86abd5..7ed624c9f 100644 --- a/src/api2/config/sync.rs +++ b/src/api2/config/sync.rs @@ -9,8 +9,8 @@ use proxmox_schema::{api, param_bail}; use pbs_api_types::{ Authid, JOB_ID_SCHEMA, PRIV_DATASTORE_APPEND, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_REMOTE_AUDIT, - PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_PRUNE, PRIV_REMOTE_READ, - PROXMOX_CONFIG_DIGEST_SCHEMA, SyncJobConfig, SyncJobConfigUpdater, + PRIV_REMOTE_DATASTORE_APPEND, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_PRUNE, + PRIV_REMOTE_READ, PROXMOX_CONFIG_DIGEST_SCHEMA, SyncJobConfig, SyncJobConfigUpdater, }; use pbs_config::sync; @@ -171,7 +171,7 @@ pub fn check_sync_job_modify_access( }; // check user is allowed to create backups on remote datastore - if target_privs & PRIV_REMOTE_DATASTORE_BACKUP == 0 { + if target_privs & (PRIV_REMOTE_DATASTORE_BACKUP | PRIV_REMOTE_DATASTORE_APPEND) == 0 { return false; } diff --git a/src/api2/push.rs b/src/api2/push.rs index 16bcdf85b..8d053c00e 100644 --- a/src/api2/push.rs +++ b/src/api2/push.rs @@ -4,8 +4,8 @@ use futures::{future::FutureExt, select}; use pbs_api_types::{ Authid, BackupNamespace, CRYPT_KEY_ID_SCHEMA, DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA, GroupFilter, NS_MAX_DEPTH_REDUCED_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_READ, - PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_PRUNE, REMOTE_ID_SCHEMA, - REMOVE_VANISHED_BACKUPS_SCHEMA, RateLimitConfig, SYNC_ENCRYPTED_ONLY_SCHEMA, + PRIV_REMOTE_DATASTORE_APPEND, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_PRUNE, + REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, RateLimitConfig, SYNC_ENCRYPTED_ONLY_SCHEMA, SYNC_VERIFIED_ONLY_SCHEMA, SYNC_WORKER_THREADS_SCHEMA, TRANSFER_LAST_SCHEMA, }; use proxmox_rest_server::WorkerTask; @@ -35,8 +35,8 @@ fn check_push_privs( user_info.check_privs( auth_id, &target_acl_path, - PRIV_REMOTE_DATASTORE_BACKUP, - false, + PRIV_REMOTE_DATASTORE_BACKUP | PRIV_REMOTE_DATASTORE_APPEND, + true, )?; if delete { diff --git a/src/server/push.rs b/src/server/push.rs index 98f843a49..c99975a2b 100644 --- a/src/server/push.rs +++ b/src/server/push.rs @@ -15,8 +15,9 @@ use pbs_api_types::{ ApiVersion, ApiVersionInfo, ArchiveType, Authid, BackupArchiveName, BackupDir, BackupGroup, BackupGroupDeleteStats, BackupNamespace, CLIENT_LOG_BLOB_NAME, CryptMode, GroupFilter, GroupListItem, MANIFEST_BLOB_NAME, NamespaceListItem, Operation, PRIV_DATASTORE_BACKUP, - PRIV_DATASTORE_READ, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATASTORE_MODIFY, - PRIV_REMOTE_DATASTORE_PRUNE, RateLimitConfig, Remote, SnapshotListItem, print_store_and_ns, + PRIV_DATASTORE_READ, PRIV_REMOTE_DATASTORE_APPEND, PRIV_REMOTE_DATASTORE_BACKUP, + PRIV_REMOTE_DATASTORE_MODIFY, PRIV_REMOTE_DATASTORE_PRUNE, RateLimitConfig, Remote, + SnapshotListItem, print_store_and_ns, }; use pbs_client::{ BackupRepository, BackupStats, BackupWriter, BackupWriterOptions, HttpClient, IndexType, @@ -215,7 +216,7 @@ fn check_ns_remote_datastore_privs( let acl_path = target_namespace.remote_acl_path(¶ms.target.remote.name, params.target.repo.store()); - user_info.check_privs(¶ms.local_user, &acl_path, privs, false)?; + user_info.check_privs(¶ms.local_user, &acl_path, privs, true)?; Ok(()) } @@ -357,7 +358,8 @@ async fn check_or_create_target_namespace( // Namespace not present on target, create namespace. // Sub-namespaces have to be created by creating parent components first. - check_ns_remote_datastore_privs(params, target_namespace, PRIV_REMOTE_DATASTORE_MODIFY) + let privs = PRIV_REMOTE_DATASTORE_MODIFY | PRIV_REMOTE_DATASTORE_APPEND; + check_ns_remote_datastore_privs(params, target_namespace, privs) .context("Creating remote namespace not allowed")?; let mut parent = BackupNamespace::root(); @@ -554,7 +556,8 @@ pub(crate) async fn push_namespace( ) -> Result<(StoreProgress, SyncStats, bool), Error> { let target_namespace = params.map_to_target(namespace)?; // Check if user is allowed to perform backups on remote datastore - check_ns_remote_datastore_privs(¶ms, &target_namespace, PRIV_REMOTE_DATASTORE_BACKUP) + let privs = PRIV_REMOTE_DATASTORE_BACKUP | PRIV_REMOTE_DATASTORE_APPEND; + check_ns_remote_datastore_privs(¶ms, &target_namespace, privs) .context("Pushing to remote namespace not allowed")?; let mut list: Vec = params -- 2.47.3