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
This commit is contained in:
Marek Marczykowski-Górecki 2019-09-08 05:08:24 +02:00
parent 114f6fb250
commit db1d4b5d48
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
4 changed files with 27 additions and 9 deletions

View File

@ -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

View File

@ -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())

View File

@ -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()

View File

@ -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))