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
|
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
|
Authors
|
||||||
|
@ -910,7 +910,7 @@ class BackupRestore(object):
|
|||||||
self.username = os.path.basename(subdir)
|
self.username = os.path.basename(subdir)
|
||||||
|
|
||||||
def __init__(self, app, backup_location, backup_vm, passphrase,
|
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__()
|
super(BackupRestore, self).__init__()
|
||||||
|
|
||||||
#: qubes.Qubes instance
|
#: qubes.Qubes instance
|
||||||
@ -927,6 +927,10 @@ class BackupRestore(object):
|
|||||||
#: backup path, inside VM pointed by :py:attr:`backup_vm`
|
#: backup path, inside VM pointed by :py:attr:`backup_vm`
|
||||||
self.backup_location = backup_location
|
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
|
#: force using specific application for (de)compression, instead of
|
||||||
#: the one named in the backup header
|
#: the one named in the backup header
|
||||||
self.force_compression_filter = force_compression_filter
|
self.force_compression_filter = force_compression_filter
|
||||||
@ -973,11 +977,14 @@ class BackupRestore(object):
|
|||||||
vmproc = None
|
vmproc = None
|
||||||
if self.backup_vm is not None:
|
if self.backup_vm is not None:
|
||||||
# If APPVM, STDOUT is a PIPE
|
# If APPVM, STDOUT is a PIPE
|
||||||
vmproc = self.backup_vm.run_service('qubes.Restore')
|
if self.location_is_service:
|
||||||
vmproc.stdin.write(
|
vmproc = self.backup_vm.run_service(self.backup_location)
|
||||||
(self.backup_location.replace("\r", "").replace("\n",
|
else:
|
||||||
"") + "\n").encode())
|
vmproc = self.backup_vm.run_service('qubes.Restore')
|
||||||
vmproc.stdin.flush()
|
vmproc.stdin.write(
|
||||||
|
(self.backup_location.replace("\r", "").replace("\n",
|
||||||
|
"") + "\n").encode())
|
||||||
|
vmproc.stdin.flush()
|
||||||
|
|
||||||
# Send to tar2qfile the VMs that should be extracted
|
# Send to tar2qfile the VMs that should be extracted
|
||||||
vmproc.stdin.write((" ".join(filelist) + "\n").encode())
|
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)
|
self.app, mock.ANY, mock_restore_info)
|
||||||
mock_backup.assert_called_once_with(
|
mock_backup.assert_called_once_with(
|
||||||
self.app, '/some/path', None, 'testpass',
|
self.app, '/some/path', None, 'testpass',
|
||||||
force_compression_filter=None)
|
force_compression_filter=None, location_is_service=False)
|
||||||
self.assertAllCalled()
|
self.assertAllCalled()
|
||||||
|
|
||||||
@mock.patch('qubesadmin.tools.qvm_backup_restore.input', create=True)
|
@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)
|
app=self.app)
|
||||||
mock_backup.assert_called_once_with(
|
mock_backup.assert_called_once_with(
|
||||||
self.app, '/some/path', None, 'testpass',
|
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.assertEqual(mock_backup.return_value.options.exclude, ['test-vm2'])
|
||||||
self.assertAllCalled()
|
self.assertAllCalled()
|
||||||
|
|
||||||
|
@ -84,6 +84,10 @@ parser.add_argument("-p", "--passphrase-file", action="store",
|
|||||||
dest="pass_file", default=None,
|
dest="pass_file", default=None,
|
||||||
help="Read passphrase from file, or use '-' to read from stdin")
|
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',
|
parser.add_argument('backup_location', action='store',
|
||||||
help="Backup directory name, or command to pipe from")
|
help="Backup directory name, or command to pipe from")
|
||||||
|
|
||||||
@ -205,6 +209,9 @@ def main(args=None, app=None):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
parser.error('no such domain: {!r}'.format(args.appvm))
|
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:
|
if args.pass_file is not None:
|
||||||
pass_f = open(args.pass_file) if args.pass_file != "-" else sys.stdin
|
pass_f = open(args.pass_file) if args.pass_file != "-" else sys.stdin
|
||||||
passphrase = pass_f.readline().rstrip()
|
passphrase = pass_f.readline().rstrip()
|
||||||
@ -218,7 +225,7 @@ def main(args=None, app=None):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
backup = BackupRestore(args.app, args.backup_location,
|
backup = BackupRestore(args.app, args.backup_location,
|
||||||
appvm, passphrase,
|
appvm, passphrase, location_is_service=args.location_is_service,
|
||||||
force_compression_filter=args.compression)
|
force_compression_filter=args.compression)
|
||||||
except qubesadmin.exc.QubesException as e:
|
except qubesadmin.exc.QubesException as e:
|
||||||
parser.error_runtime(str(e))
|
parser.error_runtime(str(e))
|
||||||
|
Loading…
Reference in New Issue
Block a user