core3 move: QubesVM
This is a big commit and probably incomplete. Tests will follow.
This commit is contained in:
parent
6b7860995b
commit
41fef46db2
@ -33,6 +33,7 @@ extensions = [
|
||||
'sphinx.ext.autosummary',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.viewcode',
|
||||
@ -180,6 +181,9 @@ html_last_updated_fmt = '%d.%m.%Y'
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# html links do not work with svg!
|
||||
graphviz_output_format = 'png'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'core-admin-doc'
|
||||
|
||||
|
@ -58,7 +58,7 @@ class QubesException(Exception):
|
||||
'''Exception that can be shown to the user'''
|
||||
pass
|
||||
|
||||
class QubesVMMConnection(object):
|
||||
class VMMConnection(object):
|
||||
'''Connection to Virtual Machine Manager (libvirt)'''
|
||||
def __init__(self):
|
||||
self._libvirt_conn = None
|
||||
@ -118,13 +118,18 @@ class QubesVMMConnection(object):
|
||||
self.init_vmm_connection()
|
||||
return self._xs
|
||||
|
||||
vmm = QubesVMMConnection()
|
||||
|
||||
|
||||
class QubesHost(object):
|
||||
'''Basic information about host machine'''
|
||||
def __init__(self):
|
||||
(model, memory, cpus, mhz, nodes, socket, cores, threads) = vmm.libvirt_conn.getInfo()
|
||||
'''Basic information about host machine
|
||||
|
||||
:param Qubes app: Qubes application context (must have :py:attr:`Qubes.vmm` attribute defined)
|
||||
'''
|
||||
|
||||
def __init__(self, app):
|
||||
self._app = app
|
||||
|
||||
(model, memory, cpus, mhz, nodes, socket, cores, threads) = \
|
||||
self._app.vmm.libvirt_conn.getInfo()
|
||||
self._total_mem = long(memory)*1024
|
||||
self._no_cpus = cpus
|
||||
|
||||
@ -154,7 +159,7 @@ class QubesHost(object):
|
||||
if previous is None:
|
||||
previous_time = time.time()
|
||||
previous = {}
|
||||
info = vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||
info = self._app.vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||
for vm in info:
|
||||
previous[vm['domid']] = {}
|
||||
previous[vm['domid']]['cpu_time'] = (
|
||||
@ -164,7 +169,7 @@ class QubesHost(object):
|
||||
|
||||
current_time = time.time()
|
||||
current = {}
|
||||
info = vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||
info = self._app.vmm.xc.domain_getinfo(0, qubes_max_qid)
|
||||
for vm in info:
|
||||
current[vm['domid']] = {}
|
||||
current[vm['domid']]['cpu_time'] = (
|
||||
@ -427,18 +432,38 @@ class property(object):
|
||||
|
||||
:param str name: name of the property
|
||||
:param collections.Callable setter: if not :py:obj:`None`, this is used to initialise value; first parameter to the function is holder instance and the second is value; this is called before ``type``
|
||||
:param collections.Callable saver: function to coerce value to something readable by setter
|
||||
:param type type: if not :py:obj:`None`, value is coerced to this type
|
||||
:param object default: default value
|
||||
:param object default: default value; if callable, will be called with holder as first argument
|
||||
:param int load_stage: stage when property should be loaded (see :py:class:`Qubes` for description of stages)
|
||||
:param int order: order of evaluation (bigger order values are later)
|
||||
:param str doc: docstring; you may use RST markup
|
||||
|
||||
Setters and savers have following signatures:
|
||||
|
||||
.. :py:function:: setter(self, prop, value)
|
||||
:noindex:
|
||||
|
||||
:param self: instance of object that is holding property
|
||||
:param prop: property object
|
||||
:param value: value being assigned
|
||||
|
||||
.. :py:function:: saver(self, prop, value)
|
||||
:noindex:
|
||||
|
||||
:param self: instance of object that is holding property
|
||||
:param prop: property object
|
||||
:param value: value being saved
|
||||
:rtype: str
|
||||
:raises property.DontSave: when property should not be saved at all
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, name, setter=None, type=None, default=None,
|
||||
def __init__(self, name, setter=None, saver=None, type=None, default=None,
|
||||
load_stage=2, order=0, save_via_ref=False, doc=None):
|
||||
self.__name__ = name
|
||||
self._setter = setter
|
||||
self._saver = saver if saver is not None else (lambda self, prop, value: str(value))
|
||||
self._type = type
|
||||
self._default = default
|
||||
self.order = order
|
||||
@ -484,6 +509,12 @@ class property(object):
|
||||
if self._type is not None:
|
||||
value = self._type(value)
|
||||
|
||||
if has_oldvalue:
|
||||
instance.fire_event('property-pre-set:' + self.__name__, value, oldvalue)
|
||||
else:
|
||||
instance.fire_event('property-pre-set:' + self.__name__, value)
|
||||
|
||||
|
||||
instance._init_property(self, value)
|
||||
|
||||
if has_oldvalue:
|
||||
@ -509,13 +540,28 @@ class property(object):
|
||||
return self.__name__ == other.__name__
|
||||
|
||||
|
||||
#
|
||||
# exceptions
|
||||
#
|
||||
|
||||
class DontSave(Exception):
|
||||
'''This exception may be raised from saver to sing that property should
|
||||
not be saved.
|
||||
'''
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def dontsave(self, prop, value):
|
||||
'''Dummy saver that never saves anything.'''
|
||||
raise DontSave()
|
||||
|
||||
#
|
||||
# some setters provided
|
||||
#
|
||||
|
||||
@staticmethod
|
||||
def forbidden(self, prop, value):
|
||||
'''Property setter that forbids loading a property
|
||||
'''Property setter that forbids loading a property.
|
||||
|
||||
This is used to effectively disable property in classes which inherit
|
||||
unwanted property. When someone attempts to load such a property, it
|
||||
@ -527,6 +573,22 @@ class property(object):
|
||||
prop.__name__, self.__class__.__name__))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def bool(self, prop, value):
|
||||
'''Property setter for boolean properties.
|
||||
|
||||
It accepts (case-insensitive) ``'0'``, ``'no'`` and ``false`` as
|
||||
:py:obj:`False` and ``'1'``, ``'yes'`` and ``'true'`` as
|
||||
:py:obj:`True`.
|
||||
'''
|
||||
|
||||
lcvalue = value.lower()
|
||||
if lcvalue in ('0', 'no', 'false'): return False
|
||||
if lcvalue in ('1', 'yes', 'true'): return True
|
||||
raise ValueError('Invalid literal for boolean property: {!r}'.format(value))
|
||||
|
||||
|
||||
|
||||
class PropertyHolder(qubes.events.Emitter):
|
||||
'''Abstract class for holding :py:class:`qubes.property`
|
||||
|
||||
@ -539,8 +601,17 @@ class PropertyHolder(qubes.events.Emitter):
|
||||
|
||||
.. event:: property-set:<propname> (subject, event, name, newvalue[, oldvalue])
|
||||
|
||||
Fired when property changes state. Signature is variable, *oldvalue* is
|
||||
present only if there was an old value.
|
||||
Fired when property changes state. Signature is variable,
|
||||
*oldvalue* is present only if there was an old value.
|
||||
|
||||
:param name: Property name
|
||||
:param newvalue: New value of the property
|
||||
:param oldvalue: Old value of the property
|
||||
|
||||
.. event:: property-pre-set:<propname> (subject, event, name, newvalue[, oldvalue])
|
||||
|
||||
Fired before property changes state. Signature is variable,
|
||||
*oldvalue* is present only if there was an old value.
|
||||
|
||||
:param name: Property name
|
||||
:param newvalue: New value of the property
|
||||
@ -625,11 +696,16 @@ class PropertyHolder(qubes.events.Emitter):
|
||||
|
||||
for prop in self.get_props_list():
|
||||
try:
|
||||
value = str(getattr(self, (prop.__name__ if with_defaults else prop._attr_name)))
|
||||
value = getattr(self, (prop.__name__ if with_defaults else prop._attr_name))
|
||||
except AttributeError, e:
|
||||
# sys.stderr.write('AttributeError: {!s}\n'.format(e))
|
||||
continue
|
||||
|
||||
try:
|
||||
value = prop._saver(self, prop, value)
|
||||
except property.DontSave:
|
||||
continue
|
||||
|
||||
element = lxml.etree.Element('property', name=prop.__name__)
|
||||
if prop.save_via_ref:
|
||||
element.set('ref', value)
|
||||
@ -640,8 +716,30 @@ class PropertyHolder(qubes.events.Emitter):
|
||||
return properties
|
||||
|
||||
|
||||
import qubes.vm.qubesvm
|
||||
import qubes.vm.templatevm
|
||||
# this was clone_attrs
|
||||
def clone_properties(self, src, proplist=None):
|
||||
'''Clone properties from other object.
|
||||
|
||||
:param PropertyHolder src: source object
|
||||
:param list proplist: list of properties (:py:obj:`None` for all properties)
|
||||
'''
|
||||
|
||||
if proplist is None:
|
||||
proplist = self.get_props_list()
|
||||
else:
|
||||
proplist = [prop for prop in self.get_props_list()
|
||||
if prop.__name__ in proplist or prop in proplist]
|
||||
|
||||
for prop in self.proplist():
|
||||
try:
|
||||
self._init_property(self, prop, getattr(src, prop._attr_name))
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
self.fire_event('cloned-properties', src, proplist)
|
||||
|
||||
|
||||
import qubes.vm
|
||||
|
||||
|
||||
class VMProperty(property):
|
||||
@ -672,6 +770,10 @@ class VMProperty(property):
|
||||
super(VMProperty, self).__set__(self, instance, vm)
|
||||
|
||||
|
||||
import qubes.vm.qubesvm
|
||||
import qubes.vm.templatevm
|
||||
|
||||
|
||||
class Qubes(PropertyHolder):
|
||||
'''Main Qubes application
|
||||
|
||||
@ -742,6 +844,12 @@ class Qubes(PropertyHolder):
|
||||
#: collection of all available labels for VMs
|
||||
self.labels = {}
|
||||
|
||||
#: Connection to VMM
|
||||
self.vmm = VMMConnection()
|
||||
|
||||
#: Information about host system
|
||||
self.host = QubesHost(self)
|
||||
|
||||
self._store = store
|
||||
|
||||
try:
|
||||
@ -749,7 +857,7 @@ class Qubes(PropertyHolder):
|
||||
except IOError:
|
||||
self._init()
|
||||
|
||||
super(PropertyHolder, self).__init__(xml=lxml.etree.parse(self.qubes_store_file))
|
||||
super(Qubes, self).__init__(xml=lxml.etree.parse(self.qubes_store_file))
|
||||
|
||||
|
||||
def _open_store(self):
|
||||
|
93
qubes/config.py
Normal file
93
qubes/config.py
Normal file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/python2
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
# Copyright (C) 2014 Wojtek Porczyk <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.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
qubes_base_dir = "/var/lib/qubes"
|
||||
system_path = {
|
||||
'qubes_guid_path': '/usr/bin/qubes-guid',
|
||||
'qrexec_daemon_path': '/usr/lib/qubes/qrexec-daemon',
|
||||
'qrexec_client_path': '/usr/lib/qubes/qrexec-client',
|
||||
'qubesdb_daemon_path': '/usr/sbin/qubesdb-daemon',
|
||||
|
||||
'qubes_base_dir': qubes_base_dir,
|
||||
|
||||
# Relative to qubes_base_dir
|
||||
'qubes_appvms_dir': 'appvms',
|
||||
'qubes_templates_dir': 'vm-templates',
|
||||
'qubes_servicevms_dir': 'servicevms',
|
||||
'qubes_store_filename': 'qubes.xml',
|
||||
'qubes_kernels_base_dir': 'vm-kernels',
|
||||
|
||||
# qubes_icon_dir is obsolete
|
||||
# use QIcon.fromTheme() where applicable
|
||||
'qubes_icon_dir': '/usr/share/icons/hicolor/128x128/devices',
|
||||
|
||||
'qrexec_policy_dir': '/etc/qubes-rpc/policy',
|
||||
|
||||
'config_template_pv': '/usr/share/qubes/vm-template.xml',
|
||||
|
||||
'qubes_pciback_cmd': '/usr/lib/qubes/unbind-pci-device.sh',
|
||||
'prepare_volatile_img_cmd': '/usr/lib/qubes/prepare-volatile-img.sh',
|
||||
'monitor_layout_notify_cmd': '/usr/bin/qubes-monitor-layout-notify',
|
||||
}
|
||||
|
||||
vm_files = {
|
||||
'root_img': 'root.img',
|
||||
'rootcow_img': 'root-cow.img',
|
||||
'volatile_img': 'volatile.img',
|
||||
'clean_volatile_img': 'clean-volatile.img.tar',
|
||||
'private_img': 'private.img',
|
||||
'kernels_subdir': 'kernels',
|
||||
'firewall_conf': 'firewall.xml',
|
||||
'whitelisted_appmenus': 'whitelisted-appmenus.list',
|
||||
'updates_stat_file': 'updates.stat',
|
||||
}
|
||||
|
||||
defaults = {
|
||||
'libvirt_uri': 'xen:///',
|
||||
'memory': 400,
|
||||
'kernelopts': "nopat",
|
||||
'kernelopts_pcidevs': "nopat iommu=soft swiotlb=4096",
|
||||
|
||||
'dom0_update_check_interval': 6*3600,
|
||||
|
||||
'private_img_size': 2*1024*1024*1024,
|
||||
'root_img_size': 10*1024*1024*1024,
|
||||
|
||||
'storage_class': None,
|
||||
|
||||
# how long (in sec) to wait for VMs to shutdown,
|
||||
# before killing them (when used qvm-run with --wait option),
|
||||
'shutdown_counter_max': 60,
|
||||
|
||||
'vm_default_netmask': "255.255.255.0",
|
||||
|
||||
# Set later
|
||||
'appvm_label': None,
|
||||
'template_label': None,
|
||||
'servicevm_label': None,
|
||||
}
|
||||
|
||||
max_qid = 254
|
||||
max_netid = 254
|
54
qubes/utils.py
Normal file
54
qubes/utils.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/python2 -O
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
|
||||
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
||||
# Copyright (C) 2014 Wojtek Porczyk <woju@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.
|
||||
#
|
||||
|
||||
|
||||
# FIXME: should be outside of QubesVM?
|
||||
def get_timezone(self):
|
||||
# fc18
|
||||
if os.path.islink('/etc/localtime'):
|
||||
return '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
|
||||
# <=fc17
|
||||
elif os.path.exists('/etc/sysconfig/clock'):
|
||||
clock_config = open('/etc/sysconfig/clock', "r")
|
||||
clock_config_lines = clock_config.readlines()
|
||||
clock_config.close()
|
||||
zone_re = re.compile(r'^ZONE="(.*)"')
|
||||
for line in clock_config_lines:
|
||||
line_match = zone_re.match(line)
|
||||
if line_match:
|
||||
return line_match.group(1)
|
||||
else:
|
||||
# last resort way, some applications makes /etc/localtime
|
||||
# hardlink instead of symlink...
|
||||
tz_info = os.stat('/etc/localtime')
|
||||
if not tz_info:
|
||||
return None
|
||||
if tz_info.st_nlink > 1:
|
||||
p = subprocess.Popen(['find', '/usr/share/zoneinfo',
|
||||
'-inum', str(tz_info.st_ino)],
|
||||
stdout=subprocess.PIPE)
|
||||
tz_path = p.communicate()[0].strip()
|
||||
return tz_path.replace('/usr/share/zoneinfo/', '')
|
||||
return None
|
||||
|
@ -297,6 +297,302 @@ class BaseVM(qubes.PropertyHolder):
|
||||
self.__class__.__name__, id(self), ' '.join(proprepr))
|
||||
|
||||
|
||||
#
|
||||
# xml serialising methods
|
||||
#
|
||||
|
||||
@staticmethod
|
||||
def xml_net_dev(ip, mac, backend):
|
||||
'''Return ``<interface>`` node for libvirt xml.
|
||||
|
||||
This was previously _format_net_dev
|
||||
|
||||
:param str ip: IP address of the frontend
|
||||
:param str mac: MAC (Ethernet) address of the frontend
|
||||
:param qubes.vm.QubesVM backend: Backend domain
|
||||
:rtype: lxml.etree._Element
|
||||
'''
|
||||
|
||||
interface = lxml.etree.Element('interface', type='ethernet')
|
||||
interface.append(lxml.etree.Element('mac', address=mac))
|
||||
interface.append(lxml.etree.Element('ip', address=ip))
|
||||
interface.append(lxml.etree.Element('domain', name=backend.name))
|
||||
|
||||
return interface
|
||||
|
||||
|
||||
@staticmethod
|
||||
def xml_pci_dev(address):
|
||||
'''Return ``<hostdev>`` node for libvirt xml.
|
||||
|
||||
This was previously _format_pci_dev
|
||||
|
||||
:param str ip: IP address of the frontend
|
||||
:param str mac: MAC (Ethernet) address of the frontend
|
||||
:param qubes.vm.QubesVM backend: Backend domain
|
||||
:rtype: lxml.etree._Element
|
||||
'''
|
||||
|
||||
dev_match = re.match('([0-9a-f]+):([0-9a-f]+)\.([0-9a-f]+)', address)
|
||||
if not dev_match:
|
||||
raise QubesException("Invalid PCI device address: %s" % address)
|
||||
|
||||
hostdev = lxml.etree.Element('hostdev', type='pci', managed='yes')
|
||||
source = lxml.etree.Element('source')
|
||||
source.append(lxml.etree.Element('address',
|
||||
bus='0x' + dev_match.group(1),
|
||||
slot='0x' + dev_match.group(2),
|
||||
function='0x' + dev_match.group(3)))
|
||||
hostdev.append(source)
|
||||
return hostdev
|
||||
|
||||
#
|
||||
# old libvirt XML
|
||||
# TODO rewrite it to do proper XML synthesis via lxml.etree
|
||||
#
|
||||
|
||||
def get_config_params(self):
|
||||
'''Return parameters for libvirt's XML domain config
|
||||
|
||||
.. deprecated:: 3.0-alpha This will go away.
|
||||
'''
|
||||
|
||||
args = {}
|
||||
args['name'] = self.name
|
||||
if hasattr(self, 'kernels_dir'):
|
||||
args['kerneldir'] = self.kernels_dir
|
||||
args['uuidnode'] = '<uuid>{!r}</uuid>'.format(self.uuid) \
|
||||
if hasattr(self, 'uuid') else ''
|
||||
args['vmdir'] = self.dir_path
|
||||
args['pcidevs'] = ''.join(lxml.etree.tostring(self.xml_pci_dev(dev))
|
||||
for dev in self.devices['pci'])
|
||||
args['maxmem'] = str(self.maxmem)
|
||||
args['vcpus'] = str(self.vcpus)
|
||||
args['mem'] = str(max(self.memory, self.maxmem))
|
||||
|
||||
if 'meminfo-writer' in self.services and not self.services['meminfo-writer']:
|
||||
# If dynamic memory management disabled, set maxmem=mem
|
||||
args['maxmem'] = args['mem']
|
||||
|
||||
if self.netvm is not None:
|
||||
args['ip'] = self.ip
|
||||
args['mac'] = self.mac
|
||||
args['gateway'] = self.netvm.gateway
|
||||
args['dns1'] = self.netvm.gateway
|
||||
args['dns2'] = self.secondary_dns
|
||||
args['netmask'] = self.netmask
|
||||
args['netdev'] = lxml.etree.tostring(self.xml_net_dev(self.ip, self.mac, self.netvm))
|
||||
args['disable_network1'] = '';
|
||||
args['disable_network2'] = '';
|
||||
else:
|
||||
args['ip'] = ''
|
||||
args['mac'] = ''
|
||||
args['gateway'] = ''
|
||||
args['dns1'] = ''
|
||||
args['dns2'] = ''
|
||||
args['netmask'] = ''
|
||||
args['netdev'] = ''
|
||||
args['disable_network1'] = '<!--';
|
||||
args['disable_network2'] = '-->';
|
||||
|
||||
args.update(self.storage.get_config_params())
|
||||
|
||||
if hasattr(self, 'kernelopts'):
|
||||
args['kernelopts'] = self.kernelopts
|
||||
if self.debug:
|
||||
self.log.info("Debug mode: adding 'earlyprintk=xen' to kernel opts")
|
||||
args['kernelopts'] += ' earlyprintk=xen'
|
||||
|
||||
|
||||
def create_config_file(self, file_path=None, prepare_dvm=False):
|
||||
'''Create libvirt's XML domain config file
|
||||
|
||||
If :py:attr:`qubes.vm.qubesvm.QubesVM.uses_custom_config` is true, this
|
||||
does nothing.
|
||||
|
||||
:param str file_path: Path to file to create (default: :py:attr:`qubes.vm.qubesvm.QubesVM.conf_file`)
|
||||
:param bool prepare_dvm: If we are in the process of preparing DisposableVM
|
||||
'''
|
||||
|
||||
if file_path is None:
|
||||
file_path = self.conf_file
|
||||
if self.uses_custom_config:
|
||||
conf_appvm = open(file_path, "r")
|
||||
domain_config = conf_appvm.read()
|
||||
conf_appvm.close()
|
||||
return domain_config
|
||||
|
||||
f_conf_template = open(self.config_file_template, 'r')
|
||||
conf_template = f_conf_template.read()
|
||||
f_conf_template.close()
|
||||
|
||||
template_params = self.get_config_params()
|
||||
if prepare_dvm:
|
||||
template_params['name'] = '%NAME%'
|
||||
template_params['privatedev'] = ''
|
||||
template_params['netdev'] = re.sub(r"address='[0-9.]*'", "address='%IP%'", template_params['netdev'])
|
||||
domain_config = conf_template.format(**template_params)
|
||||
|
||||
# FIXME: This is only for debugging purposes
|
||||
old_umask = os.umask(002)
|
||||
try:
|
||||
conf_appvm = open(file_path, "w")
|
||||
conf_appvm.write(domain_config)
|
||||
conf_appvm.close()
|
||||
except:
|
||||
# Ignore errors
|
||||
pass
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
return domain_config
|
||||
|
||||
|
||||
#
|
||||
# firewall
|
||||
# TODO rewrite it, have <firewall/> node under <domain/>
|
||||
# and possibly integrate with generic policy framework
|
||||
#
|
||||
|
||||
def write_firewall_conf(self, conf):
|
||||
'''Write firewall config file.
|
||||
'''
|
||||
defaults = self.get_firewall_conf()
|
||||
expiring_rules_present = False
|
||||
for item in defaults.keys():
|
||||
if item not in conf:
|
||||
conf[item] = defaults[item]
|
||||
|
||||
root = lxml.etree.Element(
|
||||
"QubesFirewallRules",
|
||||
policy = "allow" if conf["allow"] else "deny",
|
||||
dns = "allow" if conf["allowDns"] else "deny",
|
||||
icmp = "allow" if conf["allowIcmp"] else "deny",
|
||||
yumProxy = "allow" if conf["allowYumProxy"] else "deny"
|
||||
)
|
||||
|
||||
for rule in conf["rules"]:
|
||||
# For backward compatibility
|
||||
if "proto" not in rule:
|
||||
if rule["portBegin"] is not None and rule["portBegin"] > 0:
|
||||
rule["proto"] = "tcp"
|
||||
else:
|
||||
rule["proto"] = "any"
|
||||
element = lxml.etree.Element(
|
||||
"rule",
|
||||
address=rule["address"],
|
||||
proto=str(rule["proto"]),
|
||||
)
|
||||
if rule["netmask"] is not None and rule["netmask"] != 32:
|
||||
element.set("netmask", str(rule["netmask"]))
|
||||
if rule.get("portBegin", None) is not None and \
|
||||
rule["portBegin"] > 0:
|
||||
element.set("port", str(rule["portBegin"]))
|
||||
if rule.get("portEnd", None) is not None and rule["portEnd"] > 0:
|
||||
element.set("toport", str(rule["portEnd"]))
|
||||
if "expire" in rule:
|
||||
element.set("expire", str(rule["expire"]))
|
||||
expiring_rules_present = True
|
||||
|
||||
root.append(element)
|
||||
|
||||
tree = lxml.etree.ElementTree(root)
|
||||
|
||||
try:
|
||||
old_umask = os.umask(002)
|
||||
with open(self.firewall_conf, 'w') as f:
|
||||
tree.write(f, encoding="UTF-8", pretty_print=True)
|
||||
f.close()
|
||||
os.umask(old_umask)
|
||||
except EnvironmentError as err:
|
||||
print >> sys.stderr, "{0}: save error: {1}".format(
|
||||
os.path.basename(sys.argv[0]), err)
|
||||
return False
|
||||
|
||||
# Automatically enable/disable 'yum-proxy-setup' service based on allowYumProxy
|
||||
if conf['allowYumProxy']:
|
||||
self.services['yum-proxy-setup'] = True
|
||||
else:
|
||||
if self.services.has_key('yum-proxy-setup'):
|
||||
self.services.pop('yum-proxy-setup')
|
||||
|
||||
if expiring_rules_present:
|
||||
subprocess.call(["sudo", "systemctl", "start",
|
||||
"qubes-reload-firewall@%s.timer" % self.name])
|
||||
|
||||
return True
|
||||
|
||||
def has_firewall(self):
|
||||
return os.path.exists (self.firewall_conf)
|
||||
|
||||
def get_firewall_defaults(self):
|
||||
return { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True, "allowYumProxy": False }
|
||||
|
||||
def get_firewall_conf(self):
|
||||
conf = self.get_firewall_defaults()
|
||||
|
||||
try:
|
||||
tree = lxml.etree.parse(self.firewall_conf)
|
||||
root = tree.getroot()
|
||||
|
||||
conf["allow"] = (root.get("policy") == "allow")
|
||||
conf["allowDns"] = (root.get("dns") == "allow")
|
||||
conf["allowIcmp"] = (root.get("icmp") == "allow")
|
||||
conf["allowYumProxy"] = (root.get("yumProxy") == "allow")
|
||||
|
||||
for element in root:
|
||||
rule = {}
|
||||
attr_list = ("address", "netmask", "proto", "port", "toport",
|
||||
"expire")
|
||||
|
||||
for attribute in attr_list:
|
||||
rule[attribute] = element.get(attribute)
|
||||
|
||||
if rule["netmask"] is not None:
|
||||
rule["netmask"] = int(rule["netmask"])
|
||||
else:
|
||||
rule["netmask"] = 32
|
||||
|
||||
if rule["port"] is not None:
|
||||
rule["portBegin"] = int(rule["port"])
|
||||
else:
|
||||
# backward compatibility
|
||||
rule["portBegin"] = 0
|
||||
|
||||
# For backward compatibility
|
||||
if rule["proto"] is None:
|
||||
if rule["portBegin"] > 0:
|
||||
rule["proto"] = "tcp"
|
||||
else:
|
||||
rule["proto"] = "any"
|
||||
|
||||
if rule["toport"] is not None:
|
||||
rule["portEnd"] = int(rule["toport"])
|
||||
else:
|
||||
rule["portEnd"] = None
|
||||
|
||||
if rule["expire"] is not None:
|
||||
rule["expire"] = int(rule["expire"])
|
||||
if rule["expire"] <= int(datetime.datetime.now().strftime(
|
||||
"%s")):
|
||||
continue
|
||||
else:
|
||||
del(rule["expire"])
|
||||
|
||||
del(rule["port"])
|
||||
del(rule["toport"])
|
||||
|
||||
conf["rules"].append(rule)
|
||||
|
||||
except EnvironmentError as err:
|
||||
return conf
|
||||
except (xml.parsers.expat.ExpatError,
|
||||
ValueError, LookupError) as err:
|
||||
print("{0}: load error: {1}".format(
|
||||
os.path.basename(sys.argv[0]), err))
|
||||
return None
|
||||
|
||||
return conf
|
||||
|
||||
def load(class_, D):
|
||||
cls = BaseVM[class_]
|
||||
|
@ -4,5 +4,14 @@ import qubes.vm.qubesvm
|
||||
|
||||
class AppVM(qubes.vm.qubesvm.QubesVM):
|
||||
'''Application VM'''
|
||||
|
||||
template = qubes.VMProperty('template', load_stage=4,
|
||||
vmclass=qubes.vm.templatevm.TemplateVM,
|
||||
doc='Template, on which this AppVM is based.')
|
||||
|
||||
def __init__(self, D):
|
||||
super(AppVM, self).__init__(D)
|
||||
|
||||
# Some additional checks for template based VM
|
||||
assert self.template
|
||||
self.template.appvms.add(self)
|
||||
|
1650
qubes/vm/qubesvm.py
1650
qubes/vm/qubesvm.py
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,8 @@ import qubes.vm.qubesvm
|
||||
class TemplateVM(qubes.vm.qubesvm.QubesVM):
|
||||
'''Template for AppVM'''
|
||||
|
||||
template = qubes.property('template',
|
||||
setter=qubes.property.forbidden)
|
||||
|
||||
def __init__(self, D):
|
||||
super(TemplateVM, self).__init__(D)
|
||||
|
||||
# Some additional checks for template based VM
|
||||
assert self.root_img is not None, "Missing root_img for standalone VM!"
|
||||
|
79
tests/vm/qubesvm.py
Normal file
79
tests/vm/qubesvm.py
Normal file
@ -0,0 +1,79 @@
|
||||
#!/usr/bin/python2 -O
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
sys.path.insert(0, '../../')
|
||||
|
||||
import qubes
|
||||
import qubes.vm.qubesvm
|
||||
|
||||
|
||||
class TestProp(object):
|
||||
__name__ = 'testprop'
|
||||
|
||||
|
||||
class TestVM(object):
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.installed_by_rpm = False
|
||||
|
||||
def is_running(self):
|
||||
return self.running
|
||||
|
||||
|
||||
class TC_00_setters(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.vm = TestVM()
|
||||
self.prop = TestProp()
|
||||
|
||||
|
||||
def test_000_setter_qid(self):
|
||||
self.assertEqual(
|
||||
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, 5), 5)
|
||||
|
||||
def test_001_setter_qid_lt_0(self):
|
||||
with self.assertRaises(ValueError):
|
||||
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, -1)
|
||||
|
||||
def test_002_setter_qid_gt_max(self):
|
||||
with self.assertRaises(ValueError):
|
||||
qubes.vm.qubesvm._setter_qid(self.vm, self.prop, qubes.MAX_QID + 5)
|
||||
|
||||
|
||||
def test_010_setter_name(self):
|
||||
self.assertEqual(
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'test_name-1'),
|
||||
'test_name-1')
|
||||
|
||||
def test_011_setter_name_longer_than_31(self):
|
||||
with self.assertRaises(ValueError):
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 't' * 32)
|
||||
|
||||
def test_012_setter_name_illegal_character(self):
|
||||
with self.assertRaises(ValueError):
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'test#')
|
||||
|
||||
def test_013_setter_name_first_not_letter(self):
|
||||
with self.assertRaises(ValueError):
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, '1test')
|
||||
|
||||
def test_014_setter_name_running(self):
|
||||
self.vm.running = True
|
||||
with self.assertRaises(qubes.QubesException):
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
|
||||
|
||||
def test_015_setter_name_installed_by_rpm(self):
|
||||
self.vm.installed_by_rpm = True
|
||||
with self.assertRaises(qubes.QubesException):
|
||||
qubes.vm.qubesvm._setter_name(self.vm, self.prop, 'testname')
|
||||
|
||||
|
||||
@unittest.skip('test not implemented')
|
||||
def test_020_setter_kernel(self):
|
||||
pass
|
||||
|
||||
|
||||
class TC_90_QubesVM(unittest.TestCase):
|
||||
@unittest.skip('test not implemented')
|
||||
def test_000_init(self):
|
||||
pass
|
Loading…
Reference in New Issue
Block a user