storage: added the callback pool driver

This commit is contained in:
3hhh 2020-06-25 20:06:29 +02:00
parent 784878f1f7
commit 746697ad2c
No known key found for this signature in database
GPG Key ID: EB03A691DB2F0833
4 changed files with 542 additions and 0 deletions

442
qubes/storage/callback.py Normal file
View File

@ -0,0 +1,442 @@
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2020 David Hobach <david@hobach.de>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
#
import logging
import subprocess
import importlib
import json
from shlex import quote
import qubes.storage
class CallbackPool(qubes.storage.Pool):
''' Proxy storage pool driver adding callback functionality to other pool drivers.
This way, users can extend storage pool drivers with custom functionality using the programming language of their choice.
All configuration for this pool driver must be done in `/etc/qubes_callback.json`. Each configuration ID `conf_id` can be used
to create a callback pool with e.g. `qvm-pool -o conf_id=your_conf_id -a pool_name callback`.
Check the `qubes_callback.json.example` for an overview of the available options.
Example applications of this driver:
- custom pool mounts
- encryption
- debugging
**Integration tests**:
(all of these tests assume the `qubes_callback.json.example` configuration)
Tests that should **fail**:
```
qvm-pool -a test callback
qvm-pool -o conf_id=non-existing -a test callback
qvm-pool -o conf_id=conf_id -a test callback
qvm-pool -o conf_id=testing-fail-missing-all -a test callback
qvm-pool -o conf_id=testing-fail-missing-bdriver-args -a test callback
```
Tests that should **work**:
```
qvm-pool -o conf_id=testing-succ-file-01 -a test callback
qvm-pool
ls /mnt/test01
qvm-pool -r test && sudo rm -rf /mnt/test01
echo '#!/bin/bash'$'\n''i=0 ; for arg in "$@" ; do echo "$i: $arg" >> /tmp/callback.log ; (( i++)) ; done ; exit 0' > /usr/bin/testCbLogArgs && chmod +x /usr/bin/testCbLogArgs
rm -f /tmp/callback.log
qvm-pool -o conf_id=testing-succ-file-02 -a test callback
qvm-pool
ls /mnt/test02
less /tmp/callback.log (on_ctor & on_setup should be there and in that order)
qvm-create -l red -P test test-vm
cat /tmp/callback.log (2x on_volume_create should be added)
qvm-start test-vm
qvm-volume | grep test-vm
grep test-vm /var/lib/qubes/qubes.xml
ls /mnt/test02/appvms/
cat /tmp/callback.log (2x on_volume_start should be added)
qvm-shutdown test-vm
cat /tmp/callback.log (2x on_volume_stop should be added)
#reboot
cat /tmp/callback.log (only (!) on_ctor should be there)
qvm-start test-vm
cat /tmp/callback.log (on_sinit & 2x on_volume_start should be added)
qvm-shutdown --wait test-vm && qvm-remove test-vm
qvm-pool -r test && sudo rm -rf /mnt/test02
less /tmp/callback.log (2x on_volume_stop, 2x on_volume_remove, on_destroy should be added)
qvm-pool -o conf_id=testing-succ-file-03 -a test callback
qvm-pool
ls /mnt/test03
less /tmp/callback.log (on_ctor & on_setup should be there, no more arguments)
qvm-pool -r test && sudo rm -rf /mnt/test03
less /tmp/callback.log (nothing should have been added)
#luks pool test:
#(make sure /mnt/test.key & /mnt/test.luks don't exist)
qvm-pool -o conf_id=testing-succ-file-luks -a tluks callback
ls /mnt/
qvm-pool
sudo cryptsetup status test-luks
sudo mount | grep test_luks
ls /mnt/test_luks/
qvm-create -l red -P tluks test-luks (journalctl -b0 should show two on_volume_create callbacks)
ls /mnt/test_luks/appvms/test-luks/
qvm-volume | grep test-luks
qvm-start test-luks
#reboot
grep luks /var/lib/qubes/qubes.xml
sudo cryptsetup status test-luks (should be inactive due to late on_sinit!)
qvm-start test-luks
sudo mount | grep test_luks
qvm-shutdown --wait test-luks
qvm-remove test-luks
qvm-pool -r tluks
sudo cryptsetup status test-luks
ls -l /mnt/
#ephemeral luks pool test (key in RAM / lost on reboot):
qvm-pool -o conf_id=testing-succ-file-luks-eph -a teph callback (executes setup() twice due to signal_back)
ls /mnt/
ls /mnt/ram
md5sum /mnt/ram/teph.key (1)
sudo mount|grep -E 'ram|test'
sudo cryptsetup status test-eph
qvm-create -l red -P teph test-eph (should execute two on_volume_create callbacks)
qvm-volume | grep test-eph
ls /mnt/test_eph/appvms
qvm-start test-eph
#reboot
ls /mnt/ram (should be empty)
ls /mnt/
sudo mount|grep -E 'ram|test' (should be empty)
qvm-ls | grep eph (should still have test-eph)
grep eph /var/lib/qubes/qubes.xml (should still have test-eph)
qvm-remove test-eph (should create a new encrypted pool backend)
sudo cryptsetup status test-eph
grep eph /var/lib/qubes/qubes.xml (only the pool should be left)
ls /mnt/test_eph/ (should have the appvms directory etc.)
qvm-create -l red -P teph test-eph2
ls /mnt/test_eph/appvms/
ls /mnt/ram
qvm-start test-eph2
md5sum /mnt/ram/teph.key ((2), different than in (1))
qvm-shutdown --wait test-eph2
systemctl restart qubesd
qvm-start test-eph2 (trigger storage re-init)
md5sum /mnt/ram/teph.key (same as in (2))
qvm-shutdown test-eph2
sudo umount /mnt/test_eph
qvm-create -l red -P teph test-eph-fail (must fail with error in journalctl)
ls /mnt/test_eph/ (should be empty)
systemctl restart qubesd
qvm-remove test-eph2
qvm-create -l red -P teph test-eph3
md5sum /mnt/ram/teph.key (same as in (2))
sudo mount|grep -E 'ram|test'
ls /mnt/test_eph/appvms/test_eph3
qvm-remove test-eph3
qvm-ls | grep test-eph
qvm-pool -r teph
grep eph /var/lib/qubes/qubes.xml (nothing should be left)
qvm-pool
ls /mnt/
ls /mnt/ram/ (should be empty)
```
''' # pylint: disable=protected-access
driver = 'callback'
config_path='/etc/qubes_callback.json'
def __init__(self, *, name, conf_id):
'''Constructor.
:param conf_id: Identifier as found inside the user-controlled configuration at `/etc/qubes_callback.json`.
Non-ASCII, non-alphanumeric characters may be disallowed.
**Security Note**: Depending on your RPC policy (admin.pool.Add) this constructor and its parameters
may be called from an untrusted VM (not by default though). In those cases it may be security-relevant
not to pick easily guessable `conf_id` values for your configuration as untrusted VMs may otherwise
execute callbacks meant for other pools.
'''
self._cb_ctor_done = False
assert isinstance(conf_id, str), 'conf_id is no String. VM attack?!'
self._cb_conf_id = conf_id
with open(CallbackPool.config_path) as json_file:
conf_all = json.load(json_file)
assert isinstance(conf_all, dict), 'The file %s is supposed to define a dict.' % CallbackPool.config_path
try:
self._cb_conf = conf_all[self._cb_conf_id]
except KeyError:
#we cannot throw KeyErrors as we'll otherwise generate incorrect error messages @qubes.app._get_pool()
raise NameError('The specified conf_id %s could not be found inside %s.' % (self._cb_conf_id, CallbackPool.config_path))
try:
bdriver = self._cb_conf['bdriver']
except KeyError:
raise NameError('Missing bdriver for the conf_id %s inside %s.' % (self._cb_conf_id, CallbackPool.config_path))
self._cb_cmd_arg = json.dumps(self._cb_conf, sort_keys=True, indent=2)
try:
cls = qubes.utils.get_entry_point_one(qubes.storage.STORAGE_ENTRY_POINT, bdriver)
except KeyError:
raise NameError('The driver %s was not found on your system.' % bdriver)
assert issubclass(cls, qubes.storage.Pool), 'The class %s must be a subclass of qubes.storage.Pool.' % cls
self._cb_requires_init = self._check_init()
bdriver_args = self._cb_conf.get('bdriver_args', {})
self._cb_impl = cls(name=name, **bdriver_args)
super().__init__(name=name, revisions_to_keep=int(bdriver_args.get('revisions_to_keep', 1)))
self._cb_ctor_done = True
self._callback('on_ctor')
def _check_init(self):
''' Whether or not this object requires late storage initialization via callback. '''
cmd = self._cb_conf.get('on_sinit')
if not cmd:
cmd = self._cb_conf.get('cmd')
return bool(cmd and cmd != '-')
def _init(self, callback=True):
#late initialization on first use for e.g. decryption on first usage request
#maybe TODO: if this function is meant to be run in parallel (are Pool operations asynchronous?), a function lock is required!
if callback:
self._callback('on_sinit')
self._cb_requires_init = False
def _assertInitialized(self, **kwargs):
if self._cb_requires_init:
self._init(**kwargs)
def _callback(self, cb, cb_args=[], log=logging.getLogger('qubes.storage.callback')):
'''Run a callback.
:param cb: Callback identifier string.
:param cb_args: Optional arguments to pass to the command as first arguments.
Only passed on for the generic command specified as `cmd`, not for `on_xyz` callbacks.
'''
if self._cb_ctor_done:
cmd = self._cb_conf.get(cb)
args = [] #on_xyz callbacks should never receive arguments
if not cmd:
cmd = self._cb_conf.get('cmd')
args = [ self.name, self._cb_conf['bdriver'], cb, self._cb_cmd_arg, *cb_args ]
if cmd and cmd != '-':
args = filter(None, args)
args = ' '.join(quote(str(a)) for a in args)
cmd = ' '.join(filter(None, [cmd, args]))
log.info('callback driver executing (%s, %s %s): %s' % (self._cb_conf_id, cb, cb_args, cmd))
res = subprocess.run(cmd, shell=True, check=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
#stdout & stderr are reported if the exit code check fails
log.debug('callback driver stdout (%s, %s %s): %s' % (self._cb_conf_id, cb, cb_args, res.stdout))
log.debug('callback driver stderr (%s, %s %s): %s' % (self._cb_conf_id, cb, cb_args, res.stderr))
if self._cb_conf.get('signal_back', False) is True:
self._process_signals(res.stdout, log)
def _process_signals(self, out, log=logging.getLogger('qubes.storage.callback')):
'''Process any signals found inside a string.
:param out: String to check for signals. Each signal must be on a dedicated line.
They are executed in the order they are found. Callbacks are not triggered.
'''
for line in out.splitlines():
if line == 'SIGNAL_setup':
log.info('callback driver processing SIGNAL_setup for %s' % self._cb_conf_id)
self.setup(callback=False)
def __del__(self):
s = super()
if hasattr(s, '__del__'):
s.__del__()
@property
def config(self):
return {
'name': self.name,
'driver': CallbackPool.driver,
'conf_id': self._cb_conf_id,
}
def destroy(self):
self._assertInitialized()
ret = self._cb_impl.destroy()
self._callback('on_destroy')
return ret
def init_volume(self, vm, volume_config):
return CallbackVolume(self, self._cb_impl.init_volume(vm, volume_config))
def setup(self, callback=True):
if callback:
self._callback('on_setup')
self._assertInitialized(callback=False) #setup is assumed to include initialization
return self._cb_impl.setup()
@property
def volumes(self):
for vol in self._cb_impl.volumes:
yield CallbackVolume(self, vol)
def list_volumes(self):
for vol in self._cb_impl.list_volumes():
yield CallbackVolume(self, vol)
def get_volume(self, vid):
return CallbackVolume(self, self._cb_impl.get_volume(vid))
def included_in(self, app):
if self._cb_requires_init:
return None
else:
return self._cb_impl.included_in(app)
@property
def size(self):
if self._cb_requires_init:
return None
else:
return self._cb_impl.size
@property
def usage(self):
if self._cb_requires_init:
return None
else:
return self._cb_impl.usage
#remaining method & attribute delegation ("delegation pattern")
#Convention: The methods of this object have priority over the delegated object's methods. All attributes are
# passed to the delegated object unless their name starts with '_cb_'.
def __getattr__(self, name):
#NOTE: This method is only called when an attribute cannot be resolved locally (not part of the instance,
# not part of the class tree). It is also called for methods that cannot be resolved.
return getattr(self._cb_impl, name)
def __setattr__(self, name, value):
#NOTE: This method is called on every attribute assignment.
if name.startswith('_cb_'):
super().__setattr__(name, value)
else:
setattr(self._cb_impl, name, value)
def __delattr__(self, name):
if name.startswith('_cb_'):
super().__delattr__(name)
else:
delattr(self._cb_impl, name)
class CallbackVolume:
''' Proxy volume adding callback functionality to other volumes.
Required to support the `on_sinit` callback for late storage initialization.
**Important for Developers**: Even though instances of this class behave exactly as `qubes.storage.Volume` instances,
they are no such instances (e.g. `assert isinstance(obj, qubes.storage.Volume)` will fail).
'''
def __init__(self, pool, impl):
'''Constructor.
:param pool: `CallbackPool` of this volume
:param impl: `qubes.storage.Volume` object to wrap
'''
assert isinstance(impl, qubes.storage.Volume), 'impl must be a qubes.storage.Volume instance. Found a %s instance.' % impl.__class__
assert isinstance(pool, CallbackPool), 'pool must use a qubes.storage.CallbackPool instance. Found a %s instance.' % pool.__class__
self._cb_pool = pool
self._cb_impl = impl
def _assertInitialized(self, **kwargs):
return self._cb_pool._assertInitialized(**kwargs)
def _callback(self, cb, cb_args=[], **kwargs):
vol_args = [ *cb_args, self.name, self.vid ]
return self._cb_pool._callback(cb, cb_args=vol_args, **kwargs)
def create(self):
self._assertInitialized()
self._callback('on_volume_create')
return self._cb_impl.create()
def remove(self):
self._assertInitialized()
ret = self._cb_impl.remove()
self._callback('on_volume_remove')
return ret
def resize(self, size):
self._assertInitialized()
self._callback('on_volume_resize', cb_args=[size])
return self._cb_impl.resize(size)
def start(self):
self._assertInitialized()
self._callback('on_volume_start')
return self._cb_impl.start()
def stop(self):
self._assertInitialized()
ret = self._cb_impl.stop()
self._callback('on_volume_stop')
return ret
def import_data(self):
self._assertInitialized()
self._callback('on_volume_import_data')
return self._cb_impl.import_data()
def import_data_end(self, success):
self._assertInitialized()
ret = self._cb_impl.import_data_end(success)
self._callback('on_volume_import_data_end', cb_args=[success])
return ret
def import_volume(self, src_volume):
self._assertInitialized()
self._callback('on_volume_import', cb_args=[src_volume.vid])
return self._cb_impl.import_volume(src_volume)
def is_dirty(self):
if self._cb_pool._cb_requires_init:
return False
else:
return self._cb_impl.is_dirty()
def is_outdated(self):
if self._cb_pool._cb_requires_init:
return False
else:
return self._cb_impl.is_outdated()
#remaining method & attribute delegation
def __getattr__(self, name):
if name in [ 'block_device', 'verify', 'revert', 'export' ]:
self._assertInitialized()
return getattr(self._cb_impl, name)
def __setattr__(self, name, value):
if name.startswith('_cb_'):
super().__setattr__(name, value)
else:
setattr(self._cb_impl, name, value)
def __delattr__(self, name):
if name.startswith('_cb_'):
super().__delattr__(name)
else:
delattr(self._cb_impl, name)

View File

@ -0,0 +1,97 @@
{
"conf_id":
{
"bdriver": "Backend pool driver to proxy. This can be a class name or a full module path to a class implementing `qubes.storage.Pool`.",
"bdriver_args": {
"arg name 1": "arg value 1",
"arg name 2": "arg value 2"
},
"cmd": "Default command to call when the on_[op] operations are not specified (default: None). The command is called as such: `[cmd] [name] [bdriver] [operation] [ctor params]`. [name]: name of the pool, [operation]: any of the `on_` operations from below including its arguments, [bdriver]: backend driver of the pool, [ctor params]: Parameters passed to the `bdriver` constructor in JSON format. Each parameter is on a single line for easy parsing.",
"signal_back": "Boolean (true|false) to allow the executed commands to send signals back to the callback driver (default: false). Signals must be on a dedicated line on stdout. Currently only `SIGNAL_setup` is supported. When found, it causes the callback driver to re-setup the backend pool.",
"on_sinit": "Command to call before one-time storage initialization/first usage (default: None). Called exactly once for every `qubesd` start. Can be used to override `cmd`. Pass `-` to ignore this callback entirely even if `cmd` is specified.",
"on_ctor": "Command to call after object construction (default: None). Can be used to override `cmd`. Pass `-` to ignore this callback entirely even if `cmd` is specified.",
"on_setup": "Called before creation of a new pool. Same as above otherwise.",
"on_destroy": "Called after removal of an existing pool. Same as above otherwise.",
"on_volume_create": "Called before creation of a volume for the pool. Same as above otherwise.",
"on_volume_remove": "Called after removal of a volume of the pool. Same as above otherwise.",
"on_volume_resize": "Called before resizing a volume of the pool. Same as above otherwise.",
"on_volume_start": "Called before starting a volume of the pool. Same as above otherwise.",
"on_volume_stop": "Called after stopping a volume of the pool. Same as above otherwise.",
"on_volume_import": "Called before importing a volume from elsewhere. Same as above otherwise.",
"on_volume_import_data": "Called before importing a volume from elsewhere. Same as above otherwise.",
"on_volume_import_data_end": "Called after finishing an `import_data` action. Same as above otherwise.",
"description": "Optional description for your personal reference."
},
"testing-fail-missing-all":
{
"description": "For testing purposes only."
},
"testing-fail-missing-bdriver-args":
{
"bdriver": "file",
"description": "For testing purposes only."
},
"testing-succ-file-01":
{
"bdriver": "file",
"bdriver_args": {
"dir_path": "/mnt/test01",
"revisions_to_keep": 0
},
"description": "For testing purposes only."
},
"testing-succ-file-02":
{
"bdriver": "file",
"bdriver_args": {
"dir_path": "/mnt/test02"
},
"cmd": "testCbLogArgs",
"description": "For testing purposes only."
},
"testing-succ-file-03":
{
"bdriver": "file",
"bdriver_args": {
"dir_path": "/mnt/test03"
},
"cmd": "exit 1",
"on_ctor": "testCbLogArgs on_ctor",
"on_sinit": "testCbLogArgs on_sinit",
"on_setup": "testCbLogArgs on_setup",
"on_destroy": "-",
"description": "For testing purposes only."
},
"testing-succ-file-luks":
{
"bdriver": "file",
"bdriver_args": {
"dir_path": "/mnt/test_luks"
},
"on_ctor": "logger 'testing-succ-file-luks: ctor'",
"on_setup": "set -e -o pipefail ; [ ! -e /mnt/test.key ] ; [ ! -e /mnt/test.luks ] ; dd if=/dev/random bs=100 of=/mnt/test.key iflag=fullblock count=1 ; dd if=/dev/urandom bs=1M of=/mnt/test.luks iflag=fullblock count=2048 ; cryptsetup luksFormat -q --key-file /mnt/test.key /mnt/test.luks ; cryptsetup open -q --key-file /mnt/test.key /mnt/test.luks test-luks ; mkfs -t ext4 /dev/mapper/test-luks ; mkdir -p /mnt/test_luks ; mount /dev/mapper/test-luks /mnt/test_luks",
"on_sinit": "set -e -o pipefail ; if [[ \"$(findmnt -S /dev/mapper/test-luks -n -o TARGET)\" != \"/mnt/test_luks\" ]] ; then cryptsetup status test-luks > /dev/null || cryptsetup open -q --key-file /mnt/test.key /mnt/test.luks test-luks ; mount /dev/mapper/test-luks /mnt/test_luks ; else exit 0 ; fi",
"on_destroy": "umount /mnt/test_luks && cryptsetup close test-luks ; set -e -o pipefail ; dd if=/dev/urandom bs=100 of=/mnt/test.key iflag=fullblock count=1 ; rm -f /mnt/test.key ; rm -f /mnt/test.luks ; rmdir /mnt/test_luks",
"on_volume_create": "[[ \"$(findmnt -S /dev/mapper/test-luks -n -o TARGET)\" == \"/mnt/test_luks\" ]]",
"on_volume_import": "[[ \"$(findmnt -S /dev/mapper/test-luks -n -o TARGET)\" == \"/mnt/test_luks\" ]]",
"on_volume_import_data": "[[ \"$(findmnt -S /dev/mapper/test-luks -n -o TARGET)\" == \"/mnt/test_luks\" ]]",
"on_volume_import_data_end": "[[ \"$(findmnt -S /dev/mapper/test-luks -n -o TARGET)\" == \"/mnt/test_luks\" ]]",
"description": "For testing purposes only: Showcasing seemless use of encrypted VM pools. For personal use, a dedicated script is recommended."
},
"testing-succ-file-luks-eph":
{
"bdriver": "file",
"bdriver_args": {
"dir_path": "/mnt/test_eph"
},
"signal_back": true,
"on_setup": "set -e -o pipefail ; if [[ \"$(findmnt -T /mnt/ram -o SOURCE -n)\" != \"ramfs\" ]] ; then mkdir -p /mnt/ram ; mount -t ramfs ramfs /mnt/ram ; fi ; if [[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" != \"/mnt/test_eph\" ]] ; then if [ -f /mnt/ram/teph.key ] ; then cryptsetup status test-eph > /dev/null || cryptsetup open -q --key-file /mnt/ram/teph.key /mnt/teph.luks test-eph ; else dd if=/dev/random bs=100 of=/mnt/ram/teph.key iflag=fullblock count=1 ; if [ ! -f /mnt/teph.luks ] ; then dd if=/dev/urandom bs=1M of=/mnt/teph.luks iflag=fullblock count=2048 ; fi ; cryptsetup luksFormat -q --key-file /mnt/ram/teph.key /mnt/teph.luks ;cryptsetup open -q --key-file /mnt/ram/teph.key /mnt/teph.luks test-eph ; mkfs -t ext4 /dev/mapper/test-eph ; echo SIGNAL_setup ; fi ; mkdir -p /mnt/test_eph ; mount /dev/mapper/test-eph /mnt/test_eph ; fi ; exit 0",
"on_sinit": "set -e -o pipefail ; if [[ \"$(findmnt -T /mnt/ram -o SOURCE -n)\" != \"ramfs\" ]] ; then mkdir -p /mnt/ram ; mount -t ramfs ramfs /mnt/ram ; fi ; if [[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" != \"/mnt/test_eph\" ]] ; then if [ -f /mnt/ram/teph.key ] ; then cryptsetup status test-eph > /dev/null || cryptsetup open -q --key-file /mnt/ram/teph.key /mnt/teph.luks test-eph ; else dd if=/dev/random bs=100 of=/mnt/ram/teph.key iflag=fullblock count=1 ; if [ ! -f /mnt/teph.luks ] ; then dd if=/dev/urandom bs=1M of=/mnt/teph.luks iflag=fullblock count=2048 ; fi ; cryptsetup luksFormat -q --key-file /mnt/ram/teph.key /mnt/teph.luks ;cryptsetup open -q --key-file /mnt/ram/teph.key /mnt/teph.luks test-eph ; mkfs -t ext4 /dev/mapper/test-eph ; echo SIGNAL_setup ; fi ; mkdir -p /mnt/test_eph ; mount /dev/mapper/test-eph /mnt/test_eph ; fi ; exit 0",
"on_destroy": "umount /mnt/test_eph && cryptsetup close test-eph ; set -e -o pipefail ; if [ -f /mnt/ram/teph.key ] ; then dd if=/dev/urandom bs=100 of=/mnt/ram/teph.key iflag=fullblock count=1 ; rm -f /mnt/ram/teph.key ; fi ; rm -f /mnt/teph.luks ; rmdir /mnt/test_eph",
"on_volume_create": "[[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" == \"/mnt/test_eph\" ]]",
"on_volume_import": "[[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" == \"/mnt/test_eph\" ]]",
"on_volume_import_data": "[[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" == \"/mnt/test_eph\" ]]",
"on_volume_import_data_end": "[[ \"$(findmnt -S /dev/mapper/test-eph -n -o TARGET)\" == \"/mnt/test_eph\" ]]",
"description": "For testing purposes only: Showcasing seemless use of encrypted disposable (upon boot) VM pools with keys held in RAM only. For personal use, a dedicated script is recommended."
}
}

View File

@ -401,6 +401,8 @@ done
%{python3_sitelib}/qubes/storage/reflink.py
%{python3_sitelib}/qubes/storage/kernels.py
%{python3_sitelib}/qubes/storage/lvm.py
%{python3_sitelib}/qubes/storage/callback.py
%{python3_sitelib}/qubes/storage/qubes_callback.json.example
%dir %{python3_sitelib}/qubes/tools
%dir %{python3_sitelib}/qubes/tools/__pycache__

View File

@ -80,6 +80,7 @@ if __name__ == '__main__':
'file-reflink = qubes.storage.reflink:ReflinkPool',
'linux-kernel = qubes.storage.kernels:LinuxKernel',
'lvm_thin = qubes.storage.lvm:ThinPool',
'callback = qubes.storage.callback:CallbackPool',
],
'qubes.tests.storage': [
'test = qubes.tests.storage:TestPool',