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 40FE81FF13B for ; Wed, 03 Jun 2026 20:05:12 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 803AD15972; Wed, 3 Jun 2026 20:05:11 +0200 (CEST) From: Stoiko Ivanov To: pmg-devel@lists.proxmox.com Subject: [PATCH pmg-gui 13/15] ui: pbs remote: allow to downloading/print new encryption key Date: Wed, 3 Jun 2026 20:03:15 +0200 Message-ID: <20260603180445.98770-14-s.ivanov@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260603180445.98770-1-s.ivanov@proxmox.com> References: <20260603180445.98770-1-s.ivanov@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1780509860038 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.087 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: TBQVQQNICR2LZTJV6QNU336GCSQDPVU6 X-Message-ID-Hash: TBQVQQNICR2LZTJV6QNU336GCSQDPVU6 X-MailFrom: s.ivanov@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 Mail Gateway development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: taken from pve-manager/www/manager6/storage/PBSEdit.js introduced there in commit d1a7c6ee ("ui: storage/PBS: allow to download/print new encryption key") minimally adapted (rename storage-id/sid to remote-id/rid, renaming of utils class). Signed-off-by: Stoiko Ivanov --- js/PBSRemoteEdit.js | 211 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/js/PBSRemoteEdit.js b/js/PBSRemoteEdit.js index aa51184..dfcf9a8 100644 --- a/js/PBSRemoteEdit.js +++ b/js/PBSRemoteEdit.js @@ -1,3 +1,201 @@ +Ext.define('PMG.PBSKeyShow', { + extend: 'Ext.window.Window', + xtype: 'pmgPBSKeyShow', + mixins: ['Proxmox.Mixin.CBind'], + + width: 600, + modal: true, + resizable: false, + title: gettext('Important: Save your Encryption Key'), + + // avoid that esc closes this by mistake, force user to more manual action + onEsc: Ext.emptyFn, + closable: false, + + items: [ + { + xtype: 'form', + layout: { + type: 'vbox', + align: 'stretch', + }, + bodyPadding: 10, + border: false, + defaults: { + anchor: '100%', + border: false, + padding: '10 0 0 0', + }, + items: [ + { + xtype: 'textfield', + fieldLabel: gettext('Key'), + labelWidth: 80, + inputId: 'encryption-key-value', + cbind: { + value: '{key}', + }, + editable: false, + }, + { + xtype: 'component', + html: + gettext( + 'Keep your encryption key safe, but easily accessible for disaster recovery.', + ) + + '
' + + gettext('We recommend the following safe-keeping strategy:'), + }, + { + xtyp: 'container', + layout: 'hbox', + items: [ + { + xtype: 'component', + html: '1. ' + gettext('Save the key in your password manager.'), + flex: 1, + }, + { + xtype: 'button', + text: gettext('Copy Key'), + iconCls: 'fa fa-clipboard x-btn-icon-el-default-toolbar-small', + cls: 'x-btn-default-toolbar-small proxmox-inline-button', + width: 110, + handler: function (b) { + document.getElementById('encryption-key-value').select(); + document.execCommand('copy'); + }, + }, + ], + }, + { + xtype: 'container', + layout: 'hbox', + items: [ + { + xtype: 'component', + html: + '2. ' + + gettext( + 'Download the key to a USB (pen) drive, placed in secure vault.', + ), + flex: 1, + }, + { + xtype: 'button', + text: gettext('Download'), + iconCls: 'fa fa-download x-btn-icon-el-default-toolbar-small', + cls: 'x-btn-default-toolbar-small proxmox-inline-button', + width: 110, + handler: function (b) { + let win = this.up('window'); + + let pmgID = Proxmox.NodeName || window.location.hostname; + let name = `pmg-${pmgID}-remote-${win.rid}.enc`; + + let hiddenElement = document.createElement('a'); + hiddenElement.href = 'data:attachment/text,' + encodeURI(win.key); + hiddenElement.target = '_blank'; + hiddenElement.download = name; + hiddenElement.click(); + }, + }, + ], + }, + { + xtype: 'container', + layout: 'hbox', + items: [ + { + xtype: 'component', + html: + '3. ' + + gettext('Print as paperkey, laminated and placed in secure vault.'), + flex: 1, + }, + { + xtype: 'button', + text: gettext('Print Key'), + iconCls: 'fa fa-print x-btn-icon-el-default-toolbar-small', + cls: 'x-btn-default-toolbar-small proxmox-inline-button', + width: 110, + handler: function (b) { + let win = this.up('window'); + win.paperkey(win.key); + }, + }, + ], + }, + ], + }, + { + xtype: 'component', + border: false, + padding: '10 10 10 10', + userCls: 'pmx-hint', + html: gettext( + 'Please save the encryption key - losing it will render any backup created with it unusable', + ), + }, + ], + buttons: [ + { + text: gettext('Close'), + handler: function (b) { + let win = this.up('window'); + win.close(); + }, + }, + ], + paperkey: function (keyString) { + let me = this; + + const key = JSON.parse(keyString); + + const qrwidth = 500; + let qrdiv = document.createElement('div'); + let qrcode = new QRCode(qrdiv, { + width: qrwidth, + height: qrwidth, + correctLevel: QRCode.CorrectLevel.H, + }); + qrcode.makeCode(keyString); + + let shortKeyFP = ''; + if (key.fingerprint) { + shortKeyFP = PMG.Utils.render_pbs_fingerprint(key.fingerprint); + } + + let printFrame = document.createElement('iframe'); + Object.assign(printFrame.style, { + position: 'fixed', + right: '0', + bottom: '0', + width: '0', + height: '0', + border: '0', + }); + const prettifiedKey = JSON.stringify(key, null, 2); + const keyQrBase64 = qrdiv.children[0].toDataURL('image/png'); + const html = ` +

Encryption Key - Remote '${me.rid}' (${shortKeyFP})

+

+-----BEGIN PROXMOX BACKUP KEY----- +${prettifiedKey} +-----END PROXMOX BACKUP KEY-----

+
+ `; + + printFrame.src = 'data:text/html;base64,' + btoa(html); + document.body.appendChild(printFrame); + me.on('destroy', () => document.body.removeChild(printFrame)); + }, +}); + Ext.define('PMG.panel.PBSEncryptionKeyTab', { extend: 'Proxmox.panel.InputPanel', xtype: 'pmgPBSEncryptionKeyTab', @@ -441,6 +639,19 @@ Ext.define('PMG.PBSEdit', { bodyPadding: 0, + apiCallDone: function (success, response, options) { + let res = response.result.data; + if (!(res && res.config && res.config['encryption-key'])) { + return; + } + let key = res.config['encryption-key']; + Ext.create('PMG.PBSKeyShow', { + autoShow: true, + rid: res.remote, + key: key, + }); + }, + initComponent: function () { let me = this; -- 2.47.3