From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 853981FF141 for ; Tue, 05 May 2026 10:13:38 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 67F8419C78; Tue, 5 May 2026 10:13:38 +0200 (CEST) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Tue, 05 May 2026 10:12:59 +0200 Message-Id: Subject: Re: [PATCH proxmox-backup 4/6] sync: wire up strict encryption mode To: =?utf-8?q?Fabian_Gr=C3=BCnbichler?= , X-Mailer: aerc 0.20.0 References: <20260429140941.3537494-1-f.gruenbichler@proxmox.com> <20260429140941.3537494-5-f.gruenbichler@proxmox.com> In-Reply-To: <20260429140941.3537494-5-f.gruenbichler@proxmox.com> From: "Shannon Sterz" X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1777968674070 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.119 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy 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 Message-ID-Hash: HLDIEXADOVSA6PVNHJP6AL4EXFD4AOKQ X-Message-ID-Hash: HLDIEXADOVSA6PVNHJP6AL4EXFD4AOKQ X-MailFrom: s.sterz@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: On Wed Apr 29, 2026 at 4:09 PM CEST, Fabian Gr=C3=BCnbichler wrote: > enabling it requires configured encryption/decryption keys, disabling or > unsetting it does not. > > Signed-off-by: Fabian Gr=C3=BCnbichler > --- > src/api2/config/sync.rs | 30 +++++++++++++++++++++++++++++- > src/api2/pull.rs | 13 +++++++++---- > src/api2/push.rs | 10 ++++++++-- > src/server/sync.rs | 2 +- > 4 files changed, 47 insertions(+), 8 deletions(-) > > diff --git a/src/api2/config/sync.rs b/src/api2/config/sync.rs > index eb82745d9..ac1e6350e 100644 > --- a/src/api2/config/sync.rs > +++ b/src/api2/config/sync.rs > @@ -276,9 +276,16 @@ pub fn create_sync_job( > .unwrap_or_else(|| Authid::root_auth_id()); > > if sync_direction =3D=3D SyncDirection::Push { > + if (config.strict_encryption_mode =3D=3D Some(true)) !=3D config= .active_encryption_key.is_some() { > + bail!("'strict-encryption-mode' requires encryption key"); > + } is this also supposed to trigger if strict-encryption-mode is false or not specified, but an active encryption key is set? if i'm not misreading this, that's what would happen here. if no maybe: (data.strict_encryption_mode =3D=3D Some(true)) && !data.active_encrypt= ion_key.is_some() would be slightly more legible? > sync_user_can_access_optional_key(config.active_encryption_key.a= s_deref(), owner, true)?; > } else { > - for key in config.associated_key.as_deref().unwrap_or(&[]) { > + let keys =3D config.associated_key.as_deref().unwrap_or(&[]); > + if (config.strict_encryption_mode =3D=3D Some(true)) =3D=3D keys= .is_empty() { > + bail!("'strict-encryption-mode' requires encryption key(s)")= ; same here, is this supposed to fail when strict-mode is false and no keys are specified? > + } > + for key in keys { > sync_user_can_access_optional_key(Some(key), owner, false)?; > } > } > @@ -398,6 +405,8 @@ pub enum DeletableProperty { > ActiveEncryptionKey, > /// Delete associated key property, > AssociatedKey, > + /// Delete strict encryption_mode property, > + StrictEncryptionMode, > } > > #[api( > @@ -538,6 +547,9 @@ pub fn update_sync_job( > // Previous active encryption key might be added as = associated below. > data.associated_key =3D None; > } > + DeletableProperty::StrictEncryptionMode =3D> { > + data.strict_encryption_mode =3D None; > + } > } > } > keep_previous_key_as_associated( > @@ -654,6 +666,21 @@ pub fn update_sync_job( > data.associated_key =3D Some(associated_key); > } > > + if let Some(strict_encryption_mode) =3D update.strict_encryption_mod= e { > + data.strict_encryption_mode =3D Some(strict_encryption_mode); > + } > + > + if data.sync_direction =3D=3D Some(SyncDirection::Push) { > + if (data.strict_encryption_mode =3D=3D Some(true)) !=3D data.act= ive_encryption_key.is_some() { > + bail!("'strict-encryption-mode' requires encryption key"); see above > + } > + } else { > + let keys =3D data.associated_key.as_deref().unwrap_or(&[]); > + if (data.strict_encryption_mode =3D=3D Some(true)) =3D=3D keys.i= s_empty() { > + bail!("'strict-encryption-mode' requires encryption key(s)")= ; see above > + } > + } > + > if update.limit.rate_in.is_some() { > data.limit.rate_in =3D update.limit.rate_in; > } > @@ -829,6 +856,7 @@ acl:1:/remote/remote1/remotestore1:write@pbs:RemoteSy= ncOperator > worker_threads: None, > active_encryption_key: None, > associated_key: None, > + strict_encryption_mode: None, > }; > > // should work without ACLs > diff --git a/src/api2/pull.rs b/src/api2/pull.rs > index b80bc9a7d..2d91fbc44 100644 > --- a/src/api2/pull.rs > +++ b/src/api2/pull.rs > @@ -10,8 +10,8 @@ use pbs_api_types::{ > Authid, BackupNamespace, GroupFilter, RateLimitConfig, SyncJobConfig= , CRYPT_KEY_ID_SCHEMA, > DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA, NS_MAX_DEPTH_REDUCED_SCH= EMA, PRIV_DATASTORE_BACKUP, > PRIV_DATASTORE_PRUNE, PRIV_REMOTE_READ, REMOTE_ID_SCHEMA, REMOVE_VAN= ISHED_BACKUPS_SCHEMA, > - RESYNC_CORRUPT_SCHEMA, SYNC_ENCRYPTED_ONLY_SCHEMA, SYNC_VERIFIED_ONL= Y_SCHEMA, > - SYNC_WORKER_THREADS_SCHEMA, TRANSFER_LAST_SCHEMA, > + RESYNC_CORRUPT_SCHEMA, SYNC_ENCRYPTED_ONLY_SCHEMA, SYNC_STRICT_ENCRY= PTION_MODE_SCHEMA, > + SYNC_VERIFIED_ONLY_SCHEMA, SYNC_WORKER_THREADS_SCHEMA, TRANSFER_LAST= _SCHEMA, > }; > use pbs_config::CachedUserInfo; > use proxmox_rest_server::WorkerTask; > @@ -93,7 +93,7 @@ impl TryFrom<&SyncJobConfig> for PullParameters { > sync_job.resync_corrupt, > sync_job.worker_threads, > sync_job.associated_key.clone(), > - None, > + sync_job.strict_encryption_mode, > ) > } > } > @@ -163,6 +163,10 @@ impl TryFrom<&SyncJobConfig> for PullParameters { > }, > optional: true, > }, > + "strict-decryption-mode": { > + schema: SYNC_STRICT_ENCRYPTION_MODE_SCHEMA, > + optional: true, > + } > }, > }, > access: { > @@ -192,6 +196,7 @@ async fn pull( > resync_corrupt: Option, > worker_threads: Option, > decryption_keys: Option>, > + strict_decryption_mode: Option, > rpcenv: &mut dyn RpcEnvironment, > ) -> Result { > let auth_id: Authid =3D rpcenv.get_auth_id().unwrap().parse()?; > @@ -234,7 +239,7 @@ async fn pull( > resync_corrupt, > worker_threads, > decryption_keys, > - None, > + strict_decryption_mode, > )?; > > // fixme: set to_stdout to false? > diff --git a/src/api2/push.rs b/src/api2/push.rs > index 54dc1e4ee..2ec467161 100644 > --- a/src/api2/push.rs > +++ b/src/api2/push.rs > @@ -6,7 +6,8 @@ use pbs_api_types::{ > GROUP_FILTER_LIST_SCHEMA, NS_MAX_DEPTH_REDUCED_SCHEMA, PRIV_DATASTOR= E_BACKUP, > PRIV_DATASTORE_READ, PRIV_REMOTE_DATASTORE_BACKUP, PRIV_REMOTE_DATAS= TORE_PRUNE, > REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, SYNC_ENCRYPTED_ONL= Y_SCHEMA, > - SYNC_VERIFIED_ONLY_SCHEMA, SYNC_WORKER_THREADS_SCHEMA, TRANSFER_LAST= _SCHEMA, > + SYNC_STRICT_ENCRYPTION_MODE_SCHEMA, SYNC_VERIFIED_ONLY_SCHEMA, SYNC_= WORKER_THREADS_SCHEMA, > + TRANSFER_LAST_SCHEMA, > }; > use proxmox_rest_server::WorkerTask; > use proxmox_router::{Permission, Router, RpcEnvironment}; > @@ -116,6 +117,10 @@ fn check_push_privs( > schema: CRYPT_KEY_ID_SCHEMA, > optional: true, > }, > + "strict-encryption-mode": { > + schema: SYNC_STRICT_ENCRYPTION_MODE_SCHEMA, > + optional: true, > + } > }, > }, > access: { > @@ -143,6 +148,7 @@ async fn push( > transfer_last: Option, > worker_threads: Option, > encryption_key: Option, > + strict_encryption_mode: Option, > rpcenv: &mut dyn RpcEnvironment, > ) -> Result { > let auth_id: Authid =3D rpcenv.get_auth_id().unwrap().parse()?; > @@ -176,7 +182,7 @@ async fn push( > transfer_last, > worker_threads, > encryption_key, > - None, > + strict_encryption_mode, > ) > .await?; > > diff --git a/src/server/sync.rs b/src/server/sync.rs > index 590ad01eb..c78213c7f 100644 > --- a/src/server/sync.rs > +++ b/src/server/sync.rs > @@ -733,7 +733,7 @@ pub fn do_sync_job( > sync_job.transfer_last, > sync_job.worker_threads, > sync_job.active_encryption_key, > - None, > + sync_job.strict_encryption_mode, > ) > .await?; > push_store(push_params).await?