7516737fae
This allows to specify tight network isolation for a VM, and finally close one remaining way for leaking traffic around TorVM. Now when VM is connected to for example TorVM, its DispVMs will be also connected there. The new property can be set to: - default (uses_default_dispvm_netvm=True) - use the same NetVM/ProxyVM as the calling VM itself - including none it that's the case - None - DispVMs will be network-isolated - some NetVM/ProxyVM - will be used, even if calling VM is network-isolated Closes qubesos/qubes-issues#862
564 lines
18 KiB
Python
Executable File
564 lines
18 KiB
Python
Executable File
#!/usr/bin/python2
|
|
# -*- encoding: utf8 -*-
|
|
#
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
#
|
|
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program 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 General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
#
|
|
|
|
from qubes.qubes import QubesVmCollection
|
|
from qubes.qubes import QubesVmLabels
|
|
from qubes.qubes import QubesHost
|
|
from qubes.qubes import system_path
|
|
from optparse import OptionParser
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import re
|
|
|
|
def do_list(vm):
|
|
label_width = 19
|
|
fmt="{{0:<{0}}}: {{1}}".format(label_width)
|
|
|
|
print fmt.format ("name", vm.name)
|
|
print fmt.format ("label", vm.label.name)
|
|
print fmt.format ("type", vm.type)
|
|
if vm.template is not None:
|
|
print fmt.format ("template", vm.template.name)
|
|
if vm.netvm is not None:
|
|
print fmt.format ("netvm", vm.netvm.name)
|
|
if vm.qid != 0:
|
|
print fmt.format("dispvm_netvm", "%s%s" % (
|
|
vm.dispvm_netvm.name if vm.dispvm_netvm
|
|
else "none",
|
|
" (default)" if vm.uses_default_dispvm_netvm else ""))
|
|
print fmt.format ("updateable", vm.updateable)
|
|
print fmt.format ("autostart", vm.autostart)
|
|
print fmt.format ("installed_by_rpm", vm.installed_by_rpm)
|
|
print fmt.format ("include_in_backups", vm.include_in_backups)
|
|
print fmt.format ("last_backup", vm.backup_timestamp)
|
|
print fmt.format ("dir", vm.dir_path)
|
|
print fmt.format ("config", vm.conf_file)
|
|
print fmt.format ("pcidevs", vm.pcidevs)
|
|
if vm.template is None:
|
|
print fmt.format ("root_img", vm.root_img)
|
|
if hasattr(vm, "rootcow_img") and vm.rootcow_img is not None:
|
|
print fmt.format ("root_cow_img", vm.rootcow_img)
|
|
if vm.template is not None:
|
|
print fmt.format ("root_img", vm.template.root_img)
|
|
if hasattr(vm, 'volatile_img') and vm.volatile_img is not None:
|
|
print fmt.format ("root_volatile_img", vm.volatile_img)
|
|
|
|
if hasattr(vm, 'private_img') and vm.private_img is not None:
|
|
print fmt.format ("private_img", vm.private_img)
|
|
print fmt.format ("vcpus", str(vm.vcpus))
|
|
print fmt.format ("memory", vm.memory)
|
|
if hasattr(vm, 'maxmem'):
|
|
print fmt.format ("maxmem", vm.maxmem)
|
|
print fmt.format ("MAC", "%s%s" % (vm.mac, " (auto)" if vm._mac is None else ""))
|
|
|
|
if hasattr(vm, 'kernel'):
|
|
if vm.uses_default_kernel:
|
|
print fmt.format ("kernel", "%s (default)" % vm.kernel)
|
|
else:
|
|
print fmt.format ("kernel", vm.kernel)
|
|
|
|
if hasattr(vm, 'kernelopts'):
|
|
if vm.uses_default_kernelopts:
|
|
print fmt.format ("kernelopts", "%s (default)" % vm.kernelopts)
|
|
else:
|
|
print fmt.format ("kernelopts", vm.kernelopts)
|
|
if hasattr(vm, 'debug'):
|
|
print fmt.format("debug", "on" if vm.debug else "off")
|
|
|
|
if hasattr(vm, 'default_user'):
|
|
print fmt.format("default_user", str(vm.default_user))
|
|
|
|
if hasattr(vm, 'qrexec_installed'):
|
|
print fmt.format("qrexec_installed", str(vm.qrexec_installed))
|
|
|
|
if hasattr(vm, 'qrexec_timeout'):
|
|
print fmt.format("qrexec_timeout", str(vm.qrexec_timeout))
|
|
|
|
if hasattr(vm, 'guiagent_installed'):
|
|
print fmt.format("guiagent_installed", str(vm.guiagent_installed))
|
|
|
|
if hasattr(vm, 'seamless_gui_mode'):
|
|
print fmt.format("seamless_gui_mode", str(vm.seamless_gui_mode))
|
|
|
|
if hasattr(vm, 'drive'):
|
|
print fmt.format("drive", str(vm.drive))
|
|
|
|
if hasattr(vm, 'timezone'):
|
|
print fmt.format("timezone", str(vm.timezone))
|
|
|
|
print fmt.format ("internal", vm.internal)
|
|
|
|
def set_label(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing label name argument!"
|
|
return False
|
|
|
|
label = args[0]
|
|
if label not in QubesVmLabels:
|
|
print >> sys.stderr, "Wrong label name, supported values are the following:"
|
|
for l in QubesVmLabels.values():
|
|
print >> sys.stderr, "* {0}".format(l.name)
|
|
return False
|
|
|
|
vm.label = QubesVmLabels[label]
|
|
return True
|
|
|
|
def set_memory(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing memory argument!"
|
|
return False
|
|
|
|
new_memory = int(args[0])
|
|
|
|
if new_memory <= 0:
|
|
print >>sys.stderr, "Memory size must be positive"
|
|
return False
|
|
|
|
qubes_host = QubesHost()
|
|
if new_memory > qubes_host.memory_total/1024:
|
|
print >> sys.stderr, "This host has only {0} MB of RAM".format(qubes_host.memory_total/1024)
|
|
return False
|
|
|
|
vm.memory = new_memory
|
|
return True
|
|
|
|
def set_maxmem(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing maxmem argument!"
|
|
exit (1)
|
|
|
|
new_maxmem = int(args[0])
|
|
|
|
if new_maxmem <= 0:
|
|
print >>sys.stderr, "Memory size must be positive"
|
|
return False
|
|
|
|
qubes_host = QubesHost()
|
|
if new_maxmem > qubes_host.memory_total/1024:
|
|
print >> sys.stderr, "This host has only {0} MB of RAM".format(qubes_host.memory_total/1024)
|
|
return False
|
|
|
|
if new_maxmem < vm.memory:
|
|
print >> sys.stderr, "WARNING: new maxmem smaller than memory property - VM will be able to use only 'maxmem' memory amount"
|
|
|
|
vm.maxmem = new_maxmem
|
|
return True
|
|
|
|
def set_mac(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing MAC argument!"
|
|
return False
|
|
|
|
if not re.match("[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}|auto", args[0]):
|
|
print >> sys.stderr, "Invalid MAC argument!"
|
|
print >> sys.stderr, "Possible values:"
|
|
print >> sys.stderr, "1) auto"
|
|
print >> sys.stderr, "2) MAC in format: XX:XX:XX:XX:XX:XX"
|
|
return False
|
|
|
|
mac = args[0]
|
|
if mac == "auto":
|
|
mac = None
|
|
vm.mac = mac
|
|
return True
|
|
|
|
def set_pcidevs(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing pcidevs argument!"
|
|
return False
|
|
|
|
if vm.is_running():
|
|
print >>sys.stderr, "Cannot modify PCI devices of running VM, " \
|
|
"use qvm-pci instead"
|
|
return False
|
|
vm.pcidevs = list(eval(args[0]))
|
|
return True
|
|
|
|
def set_netvm(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing netvm name argument!"
|
|
print >> sys.stderr, "Possible values:"
|
|
print >> sys.stderr, "1) default"
|
|
print >> sys.stderr, "2) none"
|
|
print >> sys.stderr, "3) <vmaname>"
|
|
return
|
|
|
|
netvm = args[0]
|
|
if netvm == "none":
|
|
netvm = None
|
|
vm.uses_default_netvm = False
|
|
elif netvm == "default":
|
|
netvm = vms.get_default_netvm()
|
|
vm.uses_default_netvm = True
|
|
else:
|
|
netvm = vms.get_vm_by_name (netvm)
|
|
if netvm is None:
|
|
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(netvm)
|
|
return False
|
|
if not netvm.is_netvm():
|
|
print >> sys.stderr, "VM '{0}' is not a NetVM".format(netvm)
|
|
return False
|
|
vm.uses_default_netvm = False
|
|
|
|
vm.netvm = netvm
|
|
return True
|
|
|
|
def set_dispvm_netvm(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing netvm name argument!"
|
|
print >> sys.stderr, "Possible values:"
|
|
print >> sys.stderr, "1) default (the same as VM own netvm)"
|
|
print >> sys.stderr, "2) none"
|
|
print >> sys.stderr, "3) <vmaname>"
|
|
return
|
|
|
|
netvm = args[0]
|
|
if netvm == "none":
|
|
vm.netvm = None
|
|
vm.uses_default_dispvm_netvm = False
|
|
elif netvm == "default":
|
|
vm.uses_default_dispvm_netvm = True
|
|
else:
|
|
netvm = vms.get_vm_by_name (netvm)
|
|
if netvm is None:
|
|
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(netvm)
|
|
return False
|
|
if not netvm.is_netvm():
|
|
print >> sys.stderr, "VM '{0}' is not a NetVM".format(netvm)
|
|
return False
|
|
vm.dispvm_netvm = netvm
|
|
vm.uses_default_dispvm_netvm = False
|
|
return True
|
|
|
|
def set_kernel(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing kernel version argument!"
|
|
print >> sys.stderr, "Possible values:"
|
|
print >> sys.stderr, "1) default"
|
|
print >> sys.stderr, "2) none (kernels subdir in VM)"
|
|
print >> sys.stderr, "3) <kernel version>, one of:"
|
|
for k in os.listdir(system_path["qubes_kernels_base_dir"]):
|
|
print >> sys.stderr, " -", k
|
|
return False
|
|
|
|
kernel = args[0]
|
|
if kernel == "default":
|
|
kernel = vms.get_default_kernel()
|
|
vm.uses_default_kernel = True
|
|
elif kernel == "none":
|
|
kernel = None
|
|
vm.uses_default_kernel = False
|
|
else:
|
|
if not os.path.exists(os.path.join(system_path["qubes_kernels_base_dir"], kernel)):
|
|
print >> sys.stderr, "Kernel version {0} not installed.".format(kernel)
|
|
return False
|
|
vm.uses_default_kernel = False
|
|
|
|
vm.kernel = kernel
|
|
return True
|
|
|
|
def set_template(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing template name argument!"
|
|
return False
|
|
|
|
template_name = args[0];
|
|
template = vms.get_vm_by_name(template_name)
|
|
if template is None or template.qid not in vms:
|
|
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(template_name)
|
|
return False
|
|
|
|
if not template.is_template():
|
|
print >> sys.stderr, "VM '{0}' is not a TemplateVM".format(template_name)
|
|
return False
|
|
|
|
print >> sys.stderr, "Setting template for VM '{0}' to '{1}'...".format (vm.name, template_name)
|
|
vm.template = template
|
|
return True
|
|
|
|
def set_vcpus(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing vcpus count argument!"
|
|
return False
|
|
|
|
vcpus = int(args[0])
|
|
if vcpus <= 0:
|
|
print >> sys.stderr, "A vcpus count must be positive."
|
|
return False
|
|
|
|
qubes_host = QubesHost()
|
|
if vcpus > qubes_host.no_cpus:
|
|
print >> sys.stderr, "This host has only {0} cpus".format(
|
|
qubes_host.no_cpus)
|
|
return False
|
|
|
|
print >> sys.stderr, "Setting vcpus count for VM '{0}' to '{1}'...".format (vm.name, vcpus)
|
|
vm.vcpus = vcpus
|
|
return True
|
|
|
|
def set_kernelopts(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing kernel opts argument!"
|
|
print >> sys.stderr, "Possible values:"
|
|
print >> sys.stderr, "1) default"
|
|
print >> sys.stderr, "2) <opts>"
|
|
return False
|
|
|
|
if args[0] == 'default':
|
|
vm.uses_default_kernelopts = True
|
|
else:
|
|
vm.uses_default_kernelopts = False
|
|
vm.kernelopts = args[0]
|
|
|
|
return True
|
|
|
|
def set_name(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing new name!"
|
|
return False
|
|
|
|
if args[0] == vm.name:
|
|
return False
|
|
vm.set_name(args[0])
|
|
return True
|
|
|
|
def set_drive(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing new drive content (file/device)!"
|
|
return False
|
|
|
|
if args[0] == '' or args[0].lower() == 'none':
|
|
vm.drive = None
|
|
else:
|
|
vm.drive = args[0]
|
|
return True
|
|
|
|
def set_debug(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False or on/off)!"
|
|
return False
|
|
|
|
if args[0].lower() == "on":
|
|
vm.debug = True
|
|
elif args[0].lower() == "off":
|
|
vm.debug = False
|
|
else:
|
|
vm.debug = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_default_user(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing user name!"
|
|
return False
|
|
|
|
vm.default_user = args[0]
|
|
return True
|
|
|
|
def set_include_in_backups(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False)!"
|
|
return False
|
|
|
|
vm.include_in_backups = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_qrexec_installed(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False)!"
|
|
return False
|
|
|
|
vm.qrexec_installed = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_internal(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False)!"
|
|
return False
|
|
|
|
vm.internal = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_guiagent_installed(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False)!"
|
|
return False
|
|
|
|
vm.guiagent_installed = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_seamless_gui_mode(vms, vm, args):
|
|
if len(args) != 1:
|
|
print >> sys.stderr, "Missing value (true/false)!"
|
|
return False
|
|
|
|
if not args[0].lower() in ['true', 'false']:
|
|
print >> sys.stderr, "Invalid value, expected 'true' or 'false'"
|
|
return False
|
|
|
|
if args[0].lower() == 'true':
|
|
vm.seamless_gui_mode = True
|
|
else:
|
|
vm.seamless_gui_mode = False
|
|
return True
|
|
|
|
def set_autostart(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value (True/False)!"
|
|
return False
|
|
|
|
vm.autostart = bool(eval(args[0].capitalize()))
|
|
return True
|
|
|
|
def set_qrexec_timeout(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing timeout value (seconds)!"
|
|
return False
|
|
|
|
vm.qrexec_timeout = int(args[0])
|
|
return True
|
|
|
|
def set_timezone(vms, vm, args):
|
|
if len (args) != 1:
|
|
print >> sys.stderr, "Missing value ('localtime' or timeoffset in seconds)!"
|
|
return False
|
|
|
|
if not args[0].isdigit() and args[0].lower() == 'localtime':
|
|
print >> sys.stderr, "Invalid timezone value!"
|
|
return False
|
|
|
|
vm.timezone = args[0]
|
|
return True
|
|
|
|
properties = {
|
|
"include_in_backups": set_include_in_backups,
|
|
"pcidevs": set_pcidevs,
|
|
"label" : set_label,
|
|
"netvm" : set_netvm,
|
|
"dispvm_netvm" : set_dispvm_netvm,
|
|
"maxmem" : set_maxmem,
|
|
"memory" : set_memory,
|
|
"kernel" : set_kernel,
|
|
"template" : set_template,
|
|
"vcpus" : set_vcpus,
|
|
"kernelopts": set_kernelopts,
|
|
"name": set_name,
|
|
"drive": set_drive,
|
|
"mac": set_mac,
|
|
"debug": set_debug,
|
|
"default_user": set_default_user,
|
|
"qrexec_installed": set_qrexec_installed,
|
|
"guiagent_installed": set_guiagent_installed,
|
|
"seamless_gui_mode": set_seamless_gui_mode,
|
|
"qrexec_timeout": set_qrexec_timeout,
|
|
"timezone": set_timezone,
|
|
"internal": set_internal,
|
|
"autostart": set_autostart,
|
|
}
|
|
|
|
|
|
def do_set(vms, vm, property, args):
|
|
if property not in properties.keys():
|
|
print >> sys.stderr, "ERROR: Wrong property name: '{0}'".format(property)
|
|
return False
|
|
|
|
if not hasattr(vm, property):
|
|
print >> sys.stderr, "ERROR: Property '{0}' not available for this VM".format(property)
|
|
return False
|
|
|
|
try:
|
|
return properties[property](vms, vm, args)
|
|
except Exception as err:
|
|
print >> sys.stderr, "ERROR: %s" % str(err)
|
|
return False
|
|
|
|
|
|
def main():
|
|
usage = "usage: %prog -l [options] <vm-name>\n"\
|
|
"usage: %prog -s [options] <vm-name> <property> [...]\n"\
|
|
"List/set various per-VM properties."
|
|
|
|
parser = OptionParser (usage)
|
|
parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False)
|
|
parser.add_option ("-s", "--set", action="store_true", dest="do_set", default=False)
|
|
parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
|
|
help="Force to run, even with root privileges")
|
|
|
|
(options, args) = parser.parse_args ()
|
|
if (len (args) < 1):
|
|
parser.error ("You must provide at least the vmname!")
|
|
|
|
vmname = args[0]
|
|
|
|
if hasattr(os, "geteuid") and os.geteuid() == 0:
|
|
if not options.force_root:
|
|
print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems."
|
|
print >> sys.stderr, "Retry as unprivileged user."
|
|
print >> sys.stderr, "... or use --force-root to continue anyway."
|
|
exit(1)
|
|
|
|
if options.do_list and options.do_set:
|
|
print >> sys.stderr, "You cannot provide -l and -s at the same time!"
|
|
exit (1)
|
|
|
|
|
|
|
|
if options.do_set:
|
|
qvm_collection = QubesVmCollection()
|
|
qvm_collection.lock_db_for_writing()
|
|
qvm_collection.load()
|
|
else:
|
|
qvm_collection = QubesVmCollection()
|
|
qvm_collection.lock_db_for_reading()
|
|
qvm_collection.load()
|
|
qvm_collection.unlock_db()
|
|
|
|
vm = qvm_collection.get_vm_by_name(vmname)
|
|
if vm is None or vm.qid not in qvm_collection:
|
|
print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
|
|
exit(1)
|
|
|
|
if options.do_set:
|
|
if len (args) < 2:
|
|
print >> sys.stderr, "You must specify the property you wish to set..."
|
|
print >> sys.stderr, "Available properties:"
|
|
for p in properties.keys():
|
|
if hasattr(vm, p):
|
|
print >> sys.stderr, "--> '{0}'".format(p)
|
|
exit (1)
|
|
|
|
property = args[1]
|
|
if do_set(qvm_collection, vm, property, args[2:]):
|
|
qvm_collection.save()
|
|
qvm_collection.unlock_db()
|
|
else:
|
|
qvm_collection.unlock_db()
|
|
exit(1)
|
|
|
|
|
|
else:
|
|
# do_list
|
|
do_list(vm)
|
|
|
|
main()
|