backup/restore: add option to use uncommon compression filter anyway
Previous commit introduced protection against uncommon (potentially malicious) compression filters. This breaks restoring backups made with a custom compression filter. Add an option to override this check, by naming compression filter to use explicitly.
This commit is contained in:
parent
10f15e6669
commit
14f77860bf
@ -72,6 +72,13 @@ Options
|
|||||||
this limit and restore such (broken, or potentially malicious) backup
|
this limit and restore such (broken, or potentially malicious) backup
|
||||||
anyway.
|
anyway.
|
||||||
|
|
||||||
|
.. option:: --compression-filter, -Z
|
||||||
|
|
||||||
|
Force specific compression filter, instead of the one named in the backup
|
||||||
|
header. The compression filter is a command that accepts ``-d`` option to
|
||||||
|
decompress data on stdin and output it to stdout. This can be used to
|
||||||
|
override built-in protection against uncommon compression.
|
||||||
|
|
||||||
.. option:: --dest-vm=APPVM, -d APPVM
|
.. option:: --dest-vm=APPVM, -d APPVM
|
||||||
|
|
||||||
Restore from a backup located in a specific AppVM
|
Restore from a backup located in a specific AppVM
|
||||||
|
@ -192,6 +192,11 @@ class BackupHeader(object):
|
|||||||
else:
|
else:
|
||||||
raise QubesException("Unrecognized header type")
|
raise QubesException("Unrecognized header type")
|
||||||
if not header.validator(value):
|
if not header.validator(value):
|
||||||
|
if key == 'compression-filter':
|
||||||
|
raise QubesException(
|
||||||
|
"Unusual compression filter '{f}' found. Use "
|
||||||
|
"--compression-filter={f} to use it anyway.".format(
|
||||||
|
f=value))
|
||||||
raise QubesException("Invalid value for header: {}".format(key))
|
raise QubesException("Invalid value for header: {}".format(key))
|
||||||
setattr(self, header.field, value)
|
setattr(self, header.field, value)
|
||||||
|
|
||||||
@ -902,7 +907,8 @@ class BackupRestore(object):
|
|||||||
self.subdir = subdir
|
self.subdir = subdir
|
||||||
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):
|
||||||
super(BackupRestore, self).__init__()
|
super(BackupRestore, self).__init__()
|
||||||
|
|
||||||
#: qubes.Qubes instance
|
#: qubes.Qubes instance
|
||||||
@ -919,6 +925,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
|
||||||
|
|
||||||
|
#: force using specific application for (de)compression, instead of
|
||||||
|
#: the one named in the backup header
|
||||||
|
self.force_compression_filter = force_compression_filter
|
||||||
|
|
||||||
#: passphrase protecting backup integrity and optionally decryption
|
#: passphrase protecting backup integrity and optionally decryption
|
||||||
self.passphrase = passphrase
|
self.passphrase = passphrase
|
||||||
|
|
||||||
@ -1252,7 +1262,9 @@ class BackupRestore(object):
|
|||||||
"failed). Is the password correct?")
|
"failed). Is the password correct?")
|
||||||
filename = os.path.join(self.tmpdir, filename)
|
filename = os.path.join(self.tmpdir, filename)
|
||||||
with open(filename, 'rb') as f_header:
|
with open(filename, 'rb') as f_header:
|
||||||
header_data = BackupHeader(f_header.read())
|
header_data = BackupHeader(
|
||||||
|
f_header.read(),
|
||||||
|
compression_filter=self.force_compression_filter)
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
|
|
||||||
return header_data
|
return header_data
|
||||||
|
@ -58,7 +58,8 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
|
|||||||
mock_handle_broken.assert_called_once_with(
|
mock_handle_broken.assert_called_once_with(
|
||||||
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)
|
||||||
self.assertAllCalled()
|
self.assertAllCalled()
|
||||||
|
|
||||||
@mock.patch('qubesadmin.tools.qvm_backup_restore.input', create=True)
|
@mock.patch('qubesadmin.tools.qvm_backup_restore.input', create=True)
|
||||||
@ -92,7 +93,8 @@ class TC_00_qvm_backup_restore(qubesadmin.tests.QubesTestCase):
|
|||||||
qubesadmin.tools.qvm_backup_restore.main(['/some/path', 'test-vm'],
|
qubesadmin.tools.qvm_backup_restore.main(['/some/path', 'test-vm'],
|
||||||
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)
|
||||||
self.assertEqual(mock_backup.return_value.options.exclude, ['test-vm2'])
|
self.assertEqual(mock_backup.return_value.options.exclude, ['test-vm2'])
|
||||||
self.assertAllCalled()
|
self.assertAllCalled()
|
||||||
|
|
||||||
|
@ -72,6 +72,11 @@ parser.add_argument("--ignore-size-limit", action="store_true",
|
|||||||
dest="ignore_size_limit", default=False,
|
dest="ignore_size_limit", default=False,
|
||||||
help="Ignore size limit calculated from backup metadata")
|
help="Ignore size limit calculated from backup metadata")
|
||||||
|
|
||||||
|
parser.add_argument("--compression-filter", "-Z", action="store",
|
||||||
|
dest="compression",
|
||||||
|
help="Force specific compression filter program, "
|
||||||
|
"instead of the one from the backup header")
|
||||||
|
|
||||||
parser.add_argument("-d", "--dest-vm", action="store", dest="appvm",
|
parser.add_argument("-d", "--dest-vm", action="store", dest="appvm",
|
||||||
help="Specify VM containing the backup to be restored")
|
help="Specify VM containing the backup to be restored")
|
||||||
|
|
||||||
@ -213,7 +218,8 @@ 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,
|
||||||
|
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))
|
||||||
# unreachable - error_runtime will raise SystemExit
|
# unreachable - error_runtime will raise SystemExit
|
||||||
|
Loading…
Reference in New Issue
Block a user