浏览代码

backup/restore: option for alternative qrexec service

Allow setting alternative qrexec service to retrieve backup content. The
service API is slightly different than the default one: it will get only
list of files/directories to extract on its stdin, but not backup
location. The latter could be provided as a service argument, or using
other out-of-band mechanism.
This will be useful for paranoid backup restore mode, to take away
control over location/command from sandboxed qvm-backup-restore process.

QubesOS/qubes-issues#5310
Marek Marczykowski-Górecki 4 年之前
父节点
当前提交
db1d4b5d48

+ 4 - 0
doc/manpages/qvm-backup-restore.rst

@@ -87,6 +87,10 @@ Options
 
    Read passphrase from file, or use '-' to read from stdin
 
+.. option:: --location-is-service
+
+   Provided backup location is a qrexec service name (optionally with an
+   argument, separated by ``+``), instead of file path or a command.
 
 
 Authors

+ 13 - 6
qubesadmin/backup/restore.py

@@ -910,7 +910,7 @@ class BackupRestore(object):
                 self.username = os.path.basename(subdir)
 
     def __init__(self, app, backup_location, backup_vm, passphrase,
-                 force_compression_filter=None):
+                 location_is_service=False, force_compression_filter=None):
         super(BackupRestore, self).__init__()
 
         #: qubes.Qubes instance
@@ -927,6 +927,10 @@ class BackupRestore(object):
         #: backup path, inside VM pointed by :py:attr:`backup_vm`
         self.backup_location = backup_location
 
+        #: use alternative qrexec service to retrieve backup data, instead of
+        #: ``qubes.Restore`` with *backup_location* given on stdin
+        self.location_is_service = location_is_service
+
         #: force using specific application for (de)compression, instead of
         #: the one named in the backup header
         self.force_compression_filter = force_compression_filter
@@ -973,11 +977,14 @@ class BackupRestore(object):
         vmproc = None
         if self.backup_vm is not None:
             # If APPVM, STDOUT is a PIPE
-            vmproc = self.backup_vm.run_service('qubes.Restore')
-            vmproc.stdin.write(
-                (self.backup_location.replace("\r", "").replace("\n",
-                    "") + "\n").encode())
-            vmproc.stdin.flush()
+            if self.location_is_service:
+                vmproc = self.backup_vm.run_service(self.backup_location)
+            else:
+                vmproc = self.backup_vm.run_service('qubes.Restore')
+                vmproc.stdin.write(
+                    (self.backup_location.replace("\r", "").replace("\n",
+                        "") + "\n").encode())
+                vmproc.stdin.flush()
 
             # Send to tar2qfile the VMs that should be extracted
             vmproc.stdin.write((" ".join(filelist) + "\n").encode())

+ 2 - 2
qubesadmin/tests/tools/qvm_backup_restore.py

@@ -59,7 +59,7 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
                 self.app, mock.ANY, mock_restore_info)
         mock_backup.assert_called_once_with(
             self.app, '/some/path', None, 'testpass',
-            force_compression_filter=None)
+            force_compression_filter=None, location_is_service=False)
         self.assertAllCalled()
 
     @mock.patch('qubesadmin.tools.qvm_backup_restore.input', create=True)
@@ -94,7 +94,7 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
             app=self.app)
         mock_backup.assert_called_once_with(
             self.app, '/some/path', None, 'testpass',
-            force_compression_filter=None)
+            force_compression_filter=None, location_is_service=False)
         self.assertEqual(mock_backup.return_value.options.exclude, ['test-vm2'])
         self.assertAllCalled()
 

+ 8 - 1
qubesadmin/tools/qvm_backup_restore.py

@@ -84,6 +84,10 @@ parser.add_argument("-p", "--passphrase-file", action="store",
     dest="pass_file", default=None,
     help="Read passphrase from file, or use '-' to read from stdin")
 
+parser.add_argument("--location-is-service", action="store_true",
+    help="Interpret backup location as a qrexec service name,"
+         "possibly with an argument separated by +.Requires -d option.")
+
 parser.add_argument('backup_location', action='store',
     help="Backup directory name, or command to pipe from")
 
@@ -205,6 +209,9 @@ def main(args=None, app=None):
         except KeyError:
             parser.error('no such domain: {!r}'.format(args.appvm))
 
+    if args.location_is_service and not args.appvm:
+        parser.error('--location-is-service option requires -d')
+
     if args.pass_file is not None:
         pass_f = open(args.pass_file) if args.pass_file != "-" else sys.stdin
         passphrase = pass_f.readline().rstrip()
@@ -218,7 +225,7 @@ def main(args=None, app=None):
 
     try:
         backup = BackupRestore(args.app, args.backup_location,
-            appvm, passphrase,
+            appvm, passphrase, location_is_service=args.location_is_service,
             force_compression_filter=args.compression)
     except qubesadmin.exc.QubesException as e:
         parser.error_runtime(str(e))