[PATCH proxmox-backup v3 21/30] ui: expose assigning encryption key to sync jobs

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

This allows to select pre-defined encryption keys and assign them to
the sync job configuration.

Sync keys can be either assigned as active encryption key to sync
jobs in push direction, to be used when pushing new contents or
associated to a sync job in pull direction, then used to decrypt
contents with matching key fingerprint.

As active encryption key only ones with are not archived can be used,
while associations can be made also with archived keys, to still be
able do decrypt contents on pull and to avoid key deletion if
associated to either push or pull sync jobs.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 2:
- switch field label for associated keys based on sync direction.
- add comment field explaining active encryption key and associated keys and their relation on key rotation.

www/Makefile | 1 +
www/form/EncryptionKeySelector.js | 96 +++++++++++++++++++++++++++++++
www/window/SyncJobEdit.js | 62 ++++++++++++++++++++
3 files changed, 159 insertions(+)
create mode 100644 www/form/EncryptionKeySelector.js

diff --git a/www/Makefile b/www/Makefile
index 08ad50846..51da9d74e 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -55,6 +55,7 @@ JSSRC= \
form/GroupSelector.js \
form/GroupFilter.js \
form/VerifyOutdatedAfter.js \
+ form/EncryptionKeySelector.js \
data/RunningTasksStore.js \
button/TaskButton.js \
panel/PrunePanel.js \
diff --git a/www/form/EncryptionKeySelector.js b/www/form/EncryptionKeySelector.js
new file mode 100644
index 000000000..e0390e56a
--- /dev/null
+++ b/www/form/EncryptionKeySelector.js
@@ -0,0 +1,96 @@
+Ext.define('PBS.form.EncryptionKeySelector', {
+ extend: 'Ext.form.field.ComboBox',
+ alias: 'widget.pbsEncryptionKeySelector',
+
+ queryMode: 'local',
+
+ valueField: 'id',
+ displayField: 'id',
+
+ emptyText: gettext('None'),
+
+ listConfig: {
+ columns: [
+ {
+ dataIndex: 'id',
+ header: gettext('Key ID'),
+ sortable: true,
+ flex: 1,
+ },
+ {
+ dataIndex: 'created',
+ header: gettext('Created'),
+ sortable: true,
+ renderer: Proxmox.Utils.render_timestamp,
+ flex: 1,
+ },
+ {
+ dataIndex: 'archived-at',
+ header: gettext('Archived'),
+ renderer: (val) => (val ? Proxmox.Utils.render_timestamp(val) : ''),
+ sortable: true,
+ flex: 1,
+ },
+ ],
+ emptyText: ` ${gettext('No key accessible.')} `,
+ },
+
+ config: {
+ deleteEmpty: true,
+ extraRequestParams: {},
+ },
+ // override framework function to implement deleteEmpty behaviour
+ getSubmitData: function () {
+ let me = this;
+
+ let data = null;
+ if (!me.disabled && me.submitValue) {
+ let val = me.getSubmitValue();
+ if (val !== null && val !== '') {
+ data = {};
+ data[me.getName()] = val;
+ } else if (me.getDeleteEmpty()) {
+ data = {};
+ data.delete = me.getName();
+ }
+ }
+
+ return data;
+ },
+
+ triggers: {
+ clear: {
+ cls: 'pmx-clear-trigger',
+ weight: -1,
+ hidden: true,
+ handler: function () {
+ this.triggers.clear.setVisible(false);
+ this.setValue('');
+ },
+ },
+ },
+
+ listeners: {
+ change: function (field, value) {
+ let canClear = (value ?? '') !== '';
+ field.triggers.clear.setVisible(canClear);
+ },
+ },
+
+ initComponent: function () {
+ let me = this;
+
+ me.store = Ext.create('Ext.data.Store', {
+ model: 'pbs-encryption-keys',
+ autoLoad: true,
+ proxy: {
+ type: 'proxmox',
+ timeout: 30 * 1000,
+ url: `/api2/json/config/encryption-keys`,
+ extraParams: me.extraRequestParams,
+ },
+ });
+
+ me.callParent();
+ },
+});
diff --git a/www/window/SyncJobEdit.js b/www/window/SyncJobEdit.js
index 074c7855a..a944a6395 100644
--- a/www/window/SyncJobEdit.js
+++ b/www/window/SyncJobEdit.js
@@ -34,10 +34,12 @@ Ext.define('PBS.window.SyncJobEdit', {
if (me.syncDirection === 'push') {
me.subject = gettext('Sync Job - Push Direction');
me.syncDirectionPush = true;
+ me.syncCryptKeyMultiSelect = false;
me.syncRemoteLabel = gettext('Target Remote');
me.syncRemoteDatastore = gettext('Target Datastore');
me.syncRemoteNamespace = gettext('Target Namespace');
me.syncLocalOwner = gettext('Local User');
+ me.associatedKeysLabel = gettext('Associated Keys');
// Sync direction request parameter is only required for creating new jobs,
// for edit and delete it is derived from the job config given by it's id.
if (me.isCreate) {
@@ -52,6 +54,7 @@ Ext.define('PBS.window.SyncJobEdit', {
me.syncRemoteDatastore = gettext('Source Datastore');
me.syncRemoteNamespace = gettext('Source Namespace');
me.syncLocalOwner = gettext('Local Owner');
+ me.associatedKeysLabel = gettext('Decryption Keys');
}

return {};
@@ -560,6 +563,65 @@ Ext.define('PBS.window.SyncJobEdit', {
},
],
},
+ {
+ xtype: 'inputpanel',
+ title: gettext('Encryption'),
+ column1: [
+ {
+ xtype: 'pbsEncryptionKeySelector',
+ name: 'active-encryption-key',
+ fieldLabel: gettext('Active Encryption Key'),
+ multiSelect: false,
+ cbind: {
+ deleteEmpty: '{!isCreate}',
+ disabled: '{!syncDirectionPush}',
+ hidden: '{!syncDirectionPush}',
+ },
+ },
+ {
+ xtype: 'pbsEncryptionKeySelector',
+ name: 'associated-key',
+ multiSelect: true,
+ cbind: {
+ fieldLabel: '{associatedKeysLabel}',
+ deleteEmpty: '{!isCreate}',
+ },
+ extraRequestParams: {
+ 'include-archived': true,
+ },
+ },
+ ],
+ column2: [
+ {
+ xtype: 'box',
+ style: {
+ 'inline-size': '325px',
+ 'overflow-wrap': 'break-word',
+ },
+ padding: '5',
+ html: gettext(
+ 'Active encryption key is used to encrypt snapshots which are not encrypted on the source during sync. Already encrypted contents are unaffected, partially encrypted contents skipped if set.',
+ ),
+ cbind: {
+ hidden: '{!syncDirectionPush}',
+ },
+ },
+ {
+ xtype: 'box',
+ style: {
+ 'inline-size': '325px',
+ 'overflow-wrap': 'break-word',
+ },
+ padding: '5',
+ html: gettext(
+ 'Associated keys store a reference to keys in order to protect them from removal without prior disassociation. On changing the active encryption key, the previous key is added to the associated keys in order to protect from accidental deletion in case it still is required to decrypt contents.',
+ ),
+ cbind: {
+ hidden: '{!syncDirectionPush}',
+ },
+ },
+ ],
+ },
],
},
});
--
2.47.3