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:
parent
114f6fb250
commit
db1d4b5d48
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user