This is how to automatically attach USB devices that have a ID_FS_UUID property to your assigned VM.
The scripts below are based on Padhi’s scripts for attaching USB devices but hardened. Meaning:
- It will attach only devices that have been whitelisted.
- It will execute dom0 scripts
qrexec-client-vmwithout parameters, decreasing the attack surface. - It will execute only whitelisted dom0 scripts.
In the template
Install python3-systemd for accessing the logs with journalctl.
sudo apt install python3-systemd
In sys-usb (or sys-usb-dvm)
sudo mkdir /rw/config/attach-known-device
/rw/config/attach-known-device/attach_known_device.py:
#!/usr/bin/env python3
import logging
import os
import pyudev
import subprocess
from systemd import journal
import sys
FORMAT = '[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s'
logging.basicConfig(format=FORMAT)
logging.getLogger().setLevel(logging.DEBUG)
logging.StreamHandler(sys.stderr).setLevel(logging.DEBUG)
logging.debug('Level DEBUG set')
# whitelist of devices
UUID_TO_DEVICE_NICKNAME = {
# YOU NEED TO EDIT THE LINE BELOW!!!
"345b0046-5c19-1111-abcd-abe331a4a127": "backups-microsd",
}
# whitelist of dom0 qrexec scripts
DOM0_QREXEC_SCRIPTS = {
# Add and remove your own scripts for your devices
"backups-microsd-at-sdb": "custom.attach-backups-microsd",
}
def log_msg(message):
# Print on console if called from CLI, and on systemd journal otherwise
if sys.stdout.isatty():
logging.info(message)
else:
journal.send(message, SYSLOG_IDENTIFIER="attach-known-device")
def get_uuid_from_device(device_path):
full_device_path = f"/dev/{device_path}"
context = pyudev.Context()
uuid = None
try:
device = pyudev.Devices.from_device_file(context, full_device_path)
except pyudev.DeviceNotFoundByFileError:
log_msg(f"ERROR: Device file {full_device_path} not found.")
return
uuid = device.properties.get('ID_FS_UUID')
if uuid is None:
err_msg = f"ID_FS_UUID property not found in device properties."
log_msg(f"ERROR: {err_msg}")
raise ValueError(err_msg)
else:
return uuid
def attach_device_to_corresponding_vm(device_nickname, device_path):
dom0_qrexec_script = DOM0_QREXEC_SCRIPTS.get(
f"{device_nickname}-at-{device_path}"
)
if dom0_qrexec_script is None:
err_msg = (f"dom0_qrexec_script not whitelisted: "
"{dom0_qrexec_script}")
log_msg(f"ERROR: {err_msg}")
raise ValueError(err_msg)
log_msg(f"DEBUG: dom0_qrexec_script = {dom0_qrexec_script}")
args = ["qrexec-client-vm", "dom0", dom0_qrexec_script,]
log_msg(f"Executing {args}")
try:
process = subprocess.Popen(args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if stdout:
log_msg("Output:")
log_msg(f"{stdout.decode('utf-8')}")
if stderr:
log_msg(f"ERROR: {stderr.decode('utf-8')}")
except subprocess.CalledProcessError:
log_msg(f"ERROR: Failed to attach device {device_nickname} at "
"{device_path}.")
raise
if __name__ == "__main__":
log_msg("Executing attach-known-device.py")
if len(sys.argv) != 2:
log_msg("Usage: attach-known-device <device_path>"
" (e.g. sda1, not /dev/sda1)")
sys.exit(1)
device_path = sys.argv[1]
log_msg(f"Device path: {device_path}")
uuid = get_uuid_from_device(device_path)
if not uuid:
log_msg("ERROR: UUID not found.")
exit(1)
else:
log_msg(f"UUID: {uuid}")
device_nickname = UUID_TO_DEVICE_NICKNAME.get(uuid)
if device_nickname is None:
log_msg(f"INFO: unknown device: {uuid}")
exit(0)
try:
attach_device_to_corresponding_vm(device_nickname, device_path)
except subprocess.CalledProcessError:
exit(1)
/rw/config/attach-known-device/90-attach-known-device.rules:
# From https://saswat.padhi.me/blog/2021-09_usb-autoattach-in-qubes/#fabusbfa-changes-in-sysusb
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]", \
RUN+="/bin/sh -c '/rw/config/attach-known-device/attach_known_device.py %k &'"
And add this to /rw/config/rc.local:
ln -sv /rw/config/attach-known-device/90-attach-known-device.rules \
/etc/udev/rules.d/
udevadm control --reload && udevadm trigger 2>&1
In dom0
These are for my use case, but you can modify them and rename them.
/etc/qubes-rpc/custom.attach-backups-microsd:
#!/usr/bin/env bash
# Modified from https://saswat.padhi.me/blog/
# Message is displayed in sys-usb, that is calling this script with qrexec-client-vm
echo "/etc/qubes-rpc/custom.attach-microsd-backups-as-sdb.sh"
LOG="systemd-cat --identifier=attach-known-device"
echo "/etc/qubes-rpc/custom.attach-microsd-backups-as-sdb.sh" | $LOG
# Exit immediately if any of the following commands fail
set -e
#XXX race condition?
sleep 1
# Start the target VM, if needed
echo "qvm-start --skip-if-running backups" | $LOG
qvm-start --skip-if-running backups
# Attach the target device from remote VM to target VM
echo "qvm-block attach backups sys-usb:sdb" | $LOG
qvm-block attach backups sys-usb:sdb
echo "Starting qubes-backup" | $LOG
DISPLAY=:0.0 qubes-backup
{
echo "10"
sleep 1
echo "50"
sleep 1
echo "100"
} | DISPLAY=:0.0 zenity --progress --auto-close --no-cancel --text="split-bak"
if DISPLAY=:0.0 zenity --question --text="Run split-bak.sh in backups VM?"; then
qvm-run --pass-io backups -- /usr/local/bin/split-bak.sh
else
exit 0
fi
{
echo "10"
sleep 1
echo "50"
sleep 1
echo "100"
} | DISPLAY=:0.0 zenity --progress --auto-close --no-cancel --text="upload-bak"
if DISPLAY=:0.0 zenity --question --text="Run upload-bak.sh on backups?"; then
qvm-run --pass-io backups -- /usr/local/bin/upload-bak.sh
else
exit 0
fi
# Message is displayed in sys-usb, that is calling this script with qrexec-client-vm
echo "End of /etc/qubes-rpc/custom.attach-microsd-backups-as-sdb.sh"
# One more time for journalctl
echo "End of /etc/qubes-rpc/custom.attach-microsd-backups-as-sdb.sh" | $LOG
/etc/qubes-rpc/policy/custom.attach-backups-microsd:
## From https://saswat.padhi.me/blog/
sys-usb dom0 ask,default_target=dom0
@anyvm @anyvm deny