[PATCH proxmox-backup v3 15/30] api: push sync: expose optional encryption key for push sync

Christian Ebner
2026-04-14T12:59:07Z

Exposes the optional encryption key id to be used for server side
encryption of contents during push sync jobs. Only expose the
parameter for now and load the key if given, the logic to use it will
be implemented in subsequent code changes.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 2:
- also keep key id for logging

src/api2/push.rs | 8 +++++++-
src/server/push.rs | 14 ++++++++++++++
src/server/sync.rs | 1 +
3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/src/api2/push.rs b/src/api2/push.rs
index e5edc13e0..84d93621b 100644
--- a/src/api2/push.rs
+++ b/src/api2/push.rs
@@ -2,7 +2,7 @@ use anyhow::{format_err, Error};
use futures::{future::FutureExt, select};

use pbs_api_types::{
- Authid, BackupNamespace, GroupFilter, RateLimitConfig, DATASTORE_SCHEMA,
+ Authid, BackupNamespace, GroupFilter, RateLimitConfig, CRYPT_KEY_ID_SCHEMA, DATASTORE_SCHEMA,
GROUP_FILTER_LIST_SCHEMA, 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, SYNC_ENCRYPTED_ONLY_SCHEMA,
@@ -108,6 +108,10 @@ fn check_push_privs(
schema: TRANSFER_LAST_SCHEMA,
optional: true,
},
+ "encryption-key": {
+ schema: CRYPT_KEY_ID_SCHEMA,
+ optional: true,
+ },
},
},
access: {
@@ -133,6 +137,7 @@ async fn push(
verified_only: Option<bool>,
limit: RateLimitConfig,
transfer_last: Option<usize>,
+ encryption_key: Option<String>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@@ -164,6 +169,7 @@ async fn push(
verified_only,
limit,
transfer_last,
+ encryption_key,
)
.await?;

diff --git a/src/server/push.rs b/src/server/push.rs
index 0d56d2e5d..f989fdf93 100644
--- a/src/server/push.rs
+++ b/src/server/push.rs
@@ -27,6 +27,7 @@ use pbs_datastore::fixed_index::FixedIndexReader;
use pbs_datastore::index::IndexFile;
use pbs_datastore::read_chunk::AsyncReadChunk;
use pbs_datastore::{BackupManifest, DataStore, StoreProgress};
+use pbs_tools::crypt_config::CryptConfig;

use super::sync::{
check_namespace_depth_limit, exclude_not_verified_or_encrypted,
@@ -83,6 +84,9 @@ pub(crate) struct PushParameters {
verified_only: bool,
/// How many snapshots should be transferred at most (taking the newest N snapshots)
transfer_last: Option<usize>,
+ /// Encryption key to use for pushing unencrypted backup snapshots. Does not affect
+ /// already encrypted snapshots.
+ crypt_config: Option<(String, Arc<CryptConfig>)>,
}

impl PushParameters {
@@ -102,6 +106,7 @@ impl PushParameters {
verified_only: Option<bool>,
limit: RateLimitConfig,
transfer_last: Option<usize>,
+ active_encryption_key: Option<String>,
) -> Result<Self, Error> {
if let Some(max_depth) = max_depth {
ns.check_max_depth(max_depth)?;
@@ -155,6 +160,14 @@ impl PushParameters {
};
let group_filter = group_filter.unwrap_or_default();

+ let crypt_config = if let Some(key_id) = &active_encryption_key {
+ let crypt_config =
+ crate::server::sync::check_privs_and_load_key_config(key_id, &local_user, true)?;
+ crypt_config.map(|crypt_config| (key_id.to_string(), crypt_config))
+ } else {
+ None
+ };
+
Ok(Self {
source,
target,
@@ -165,6 +178,7 @@ impl PushParameters {
encrypted_only,
verified_only,
transfer_last,
+ crypt_config,
})
}

diff --git a/src/server/sync.rs b/src/server/sync.rs
index 9c070cd9c..6b84ae6d7 100644
--- a/src/server/sync.rs
+++ b/src/server/sync.rs
@@ -677,6 +677,7 @@ pub fn do_sync_job(
sync_job.verified_only,
sync_job.limit.clone(),
sync_job.transfer_last,
+ sync_job.active_encryption_key,
)
.await?;
push_store(push_params).await?
--
2.47.3