diff --git a/doc/manpages/qvm-volume.rst b/doc/manpages/qvm-volume.rst index ad95b9af..e3dafcf2 100644 --- a/doc/manpages/qvm-volume.rst +++ b/doc/manpages/qvm-volume.rst @@ -81,6 +81,12 @@ Set property of given volume. Properties currently possible to change: should be keep. At each qube shutdown its previous state is saved in new revision, and the oldest revisions are remove so that only `revisions_to_keep` are left. Set to `0` to not leave any previous versions. + - `ephemeral` - should the volume be encrypted with en ephemeral key? This can + be enabled only on a volume with `save_on_stop=False` and `snap_on_start=True` + - which is only `volatile` volume. When set, it provides a bit more + anti-forensics protection against attacker with access to the LUKS disk key. + In majority of use cases, it only degrades performance due to additional + encryption level. aliases: c, set, s diff --git a/qubesadmin/tests/tools/qvm_volume.py b/qubesadmin/tests/tools/qvm_volume.py index feefe5d8..16afb9f2 100644 --- a/qubesadmin/tests/tools/qvm_volume.py +++ b/qubesadmin/tests/tools/qvm_volume.py @@ -360,6 +360,21 @@ def test_032_set_invalid(self): app=self.app)) self.assertAllCalled() + def test_033_set_ephemeral(self): + self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \ + b'0\x00testvm class=AppVM state=Running\n' + self.app.expected_calls[ + ('testvm', 'admin.vm.volume.List', None, None)] = \ + b'0\x00root\nprivate\nvolatile\n' + self.app.expected_calls[ + ('testvm', 'admin.vm.volume.Set.ephemeral', 'volatile', + b'True')] = b'0\x00' + self.assertEqual(0, + qubesadmin.tools.qvm_volume.main( + ['set', 'testvm:volatile', 'ephemeral', 'True'], + app=self.app)) + self.assertAllCalled() + def test_040_info(self): self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \ b'0\x00testvm class=AppVM state=Running\n' @@ -377,6 +392,7 @@ def test_040_info(self): b'save_on_stop=True\n' \ b'snap_on_start=False\n' \ b'revisions_to_keep=3\n' \ + b'ephemeral=False\n' \ b'is_outdated=False\n' self.app.expected_calls[ ('testvm', 'admin.vm.volume.ListSnapshots', 'private', None)] = \ @@ -398,6 +414,7 @@ def test_040_info(self): 'size 2147483648\n' 'usage 10000000\n' 'revisions_to_keep 3\n' + 'ephemeral False\n' 'is_outdated False\n' 'Available revisions (for revert):\n' ' 200101010000\n' @@ -422,6 +439,7 @@ def test_041_info_no_revisions(self): b'save_on_stop=False\n' \ b'snap_on_start=True\n' \ b'revisions_to_keep=0\n' \ + b'ephemeral=True\n' \ b'is_outdated=False\n' self.app.expected_calls[ ('testvm', 'admin.vm.volume.ListSnapshots', 'root', None)] = \ @@ -440,6 +458,7 @@ def test_041_info_no_revisions(self): 'size 2147483648\n' 'usage 10000000\n' 'revisions_to_keep 0\n' + 'ephemeral True\n' 'is_outdated False\n' 'Available revisions (for revert): none\n') self.assertAllCalled() @@ -490,6 +509,52 @@ def test_043_info_revisions_only(self): '200301010000\n') self.assertAllCalled() + def test_044_info_no_ephemeral(self): + self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \ + b'0\x00testvm class=AppVM state=Running\n' + self.app.expected_calls[ + ('testvm', 'admin.vm.volume.List', None, None)] = \ + b'0\x00root\nprivate\n' + self.app.expected_calls[ + ('testvm', 'admin.vm.volume.Info', 'private', None)] = \ + b'0\x00pool=lvm\n' \ + b'vid=qubes_dom0/vm-testvm-private\n' \ + b'size=2147483648\n' \ + b'usage=10000000\n' \ + b'rw=True\n' \ + b'source=\n' \ + b'save_on_stop=True\n' \ + b'snap_on_start=False\n' \ + b'revisions_to_keep=3\n' \ + b'is_outdated=False\n' + self.app.expected_calls[ + ('testvm', 'admin.vm.volume.ListSnapshots', 'private', None)] = \ + b'0\x00200101010000\n200201010000\n200301010000\n' + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + self.assertEqual(0, + qubesadmin.tools.qvm_volume.main(['info', 'testvm:private'], + app=self.app)) + output = stdout.getvalue() + # travis... + output = output.replace('\nsource\n', '\nsource \n') + self.assertEqual(output, + 'pool lvm\n' + 'vid qubes_dom0/vm-testvm-private\n' + 'rw True\n' + 'source \n' + 'save_on_stop True\n' + 'snap_on_start False\n' + 'size 2147483648\n' + 'usage 10000000\n' + 'revisions_to_keep 3\n' + 'ephemeral False\n' + 'is_outdated False\n' + 'Available revisions (for revert):\n' + ' 200101010000\n' + ' 200201010000\n' + ' 200301010000\n') + self.assertAllCalled() + def test_050_import_file(self): self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \ b'0\x00testvm class=AppVM state=Running\n' diff --git a/qubesadmin/tools/qvm_volume.py b/qubesadmin/tools/qvm_volume.py index e7efff02..862866bc 100644 --- a/qubesadmin/tools/qvm_volume.py +++ b/qubesadmin/tools/qvm_volume.py @@ -94,7 +94,7 @@ def info_volume(args): volume = args.volume info_items = ( 'pool', 'vid', 'rw', 'source', 'save_on_stop', - 'snap_on_start', 'size', 'usage', 'revisions_to_keep') + 'snap_on_start', 'size', 'usage', 'revisions_to_keep', 'ephemeral') if args.property: if args.property == 'revisions': for rev in volume.revisions: @@ -131,7 +131,7 @@ def info_volume(args): def config_volume(args): """ Change property of selected volume """ volume = args.volume - if not args.property in ('rw', 'revisions_to_keep'): + if args.property not in ('rw', 'revisions_to_keep', 'ephemeral'): raise qubesadmin.exc.QubesNoSuchPropertyError( 'Invalid property: {}'.format(args.property)) setattr(volume, args.property, args.value)