firstboot: use Qubes preconfiguration infrastructure

This commit is contained in:
Wojtek Porczyk 2015-11-20 11:15:38 +01:00
parent 84f61cbb6b
commit be64e72d63

View File

@ -18,10 +18,17 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import grp
import os
import re
import string
import subprocess
import sys
import threading
import time
import gtk
import libuser
import os, string, sys, time, re
import threading, subprocess, grp
from firstboot.config import *
from firstboot.constants import *
@ -32,18 +39,74 @@ import gettext
_ = lambda x: gettext.ldgettext("firstboot", x)
N_ = lambda x: x
class moduleClass(Module):
netvm_name = "sys-net"
fwvm_name = "sys-firewall"
def is_package_installed(pkgname):
return not subprocess.call(['rpm', '-q', pkgname],
stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
class QubesChoice(object):
instances = []
def __init__(self, label, states, depend=None, extra_check=None):
self.widget = gtk.CheckButton(label)
self.states = states
self.depend = depend
self.extra_check = extra_check
self.selected = None
if self.depend is not None:
self.depend.widget.connect('toggled', self.friend_on_toggled)
self.instances.append(self)
def friend_on_toggled(self, other_widget):
self.set_sensitive(other_widget.get_active())
def get_selected(self):
return self.selected if self.selected is not None \
else self.widget.get_sensitive() and self.widget.get_active()
def store_selected(self):
self.selected = self.get_selected()
def set_sensitive(self, sensitive):
self.widget.set_sensitive(sensitive
and (self.extra_check is None or self.extra_check()))
@classmethod
def on_check_advanced_toggled(cls, widget):
selected = widget.get_active()
# this works, because you cannot instantiate the choices in wrong order
# (cls.instances is a list and have deterministic ordering)
for choice in cls.instances:
choice.set_sensitive(not selected and
(choice.depend is None or choice.depend.get_selected()))
@classmethod
def get_states(cls):
for choice in cls.instances:
if choice.get_selected():
for state in choice.states:
yield state
class moduleClass(Module):
def __init__(self):
Module.__init__(self)
self.priority = 10000
self.sidebarTitle = N_("Create Service VMs")
self.title = N_("Create Service VMs")
self.sidebarTitle = N_("Create VMs")
self.title = N_("Create VMs")
self.icon = "qubes.png"
self.admin = libuser.admin()
self.default_template = 'fedora-21'
self.choices = []
def _showErrorMessage(self, text):
dlg = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, text)
@ -57,16 +120,18 @@ class moduleClass(Module):
try:
qubes_users = self.admin.enumerateUsersByGroup('qubes')
if not self.radio_dontdoanything.get_active():
if len(qubes_users) < 1:
self._showErrorMessage(_("You must create a user account to create default VMs."))
return RESULT_FAILURE
else:
self.qubes_user = qubes_users[0]
self.radio_servicevms_and_appvms.set_sensitive(False)
self.radio_onlyservicevms.set_sensitive(False)
self.radio_dontdoanything.set_sensitive(False)
if len(qubes_users) < 1:
self._showErrorMessage(_("You must create a user account to create default VMs."))
return RESULT_FAILURE
else:
self.qubes_user = qubes_users[0]
for choice in QubesChoice.instances:
choice.store_selected()
choice.widget.set_sensitive(False)
self.check_advanced.set_sensitive(False)
interface.nextButton.set_sensitive(False)
if self.progress is None:
self.progress = gtk.ProgressBar()
@ -77,40 +142,20 @@ class moduleClass(Module):
if testing:
return RESULT_SUCCESS
self.set_default_template()
if self.radio_dontdoanything.get_active():
if self.check_advanced.get_active():
return RESULT_SUCCESS
interface.nextButton.set_sensitive(False)
errors = []
for template in os.listdir('/var/lib/qubes/vm-templates'):
try:
self.configure_template(template)
except Exception as e:
errors.append((self.stage, str(e)))
self.configure_default_template()
self.configure_qubes()
self.configure_network()
try:
self.create_default_netvm()
self.create_default_fwvm()
self.set_networking_type(netvm=True)
self.start_qubes_networking()
self.configure_default_dvm()
except Exception as e:
errors.append((self.stage, str(e)))
try:
self.create_default_dvm()
except Exception as e:
errors.append((self.stage, str(e)))
if self.radio_servicevms_and_appvms.get_active():
try:
self.create_appvms()
except Exception as e:
errors.append((self.stage, str(e)))
if errors:
msg = ""
for (stage, error) in errors:
@ -129,62 +174,16 @@ class moduleClass(Module):
self.show_stage("Failure...")
self.progress.hide()
self.radio_dontdoanything.set_active(True)
self.check_advanced.set_active(True)
interface.nextButton.set_sensitive(True)
return RESULT_FAILURE
def show_stage(self, stage):
self.stage = stage
self.progress.set_text(stage)
def set_default_template(self):
subprocess.call(['/usr/bin/qubes-prefs', '--set', 'default-template', self.default_template])
def configure_template(self, name):
self.show_stage(_("Configuring TemplateVM {}".format(name)))
self.run_in_thread(self.do_configure_template, args=(name,))
def run_in_thread(self, method, args = None):
thread = threading.Thread(target=method, args = (args if args else ()))
thread.start()
count = 0
while thread.is_alive():
self.progress.pulse()
while gtk.events_pending():
gtk.main_iteration(False)
time.sleep(0.1)
if self.process_error is not None:
raise Exception(self.process_error)
def create_default_netvm(self):
self.show_stage(_("Creating default NetworkVM"))
self.run_in_thread(self.do_create_netvm)
def create_default_fwvm(self):
self.show_stage(_("Creating default FirewallVM"))
self.run_in_thread(self.do_create_fwvm)
def create_default_dvm(self):
self.show_stage(_("Creating default DisposableVM"))
self.run_in_thread(self.do_create_dvm)
def set_networking_type(self, netvm):
if netvm:
self.show_stage(_("Setting FirewallVM + NetworkVM networking"))
self.run_in_thread(self.do_set_netvm_networking)
else:
self.show_stage(_("Setting Dom0 networking"))
self.run_in_thread(self.do_set_dom0_networking)
def start_qubes_networking (self):
self.show_stage(_("Starting Qubes networking"))
self.run_in_thread(self.do_start_networking)
def create_appvms (self):
self.show_stage(_("Creating handy AppVMs"))
self.run_in_thread(self.do_create_appvms)
def run_command(self, command, stdin=None):
try:
os.setgid(self.qubes_gid)
@ -199,21 +198,57 @@ class moduleClass(Module):
except Exception as e:
self.process_error = str(e)
def get_timezone(self):
localtime = "/etc/localtime"
zoneinfo = "/usr/share/zoneinfo/" # must end with "/"
if os.path.exists(localtime) and os.path.islink(localtime):
tzfile = os.path.realpath(localtime)
if tzfile.startswith(zoneinfo):
return tzfile[len(zoneinfo):]
return None
def run_in_thread(self, method, args = None):
thread = threading.Thread(target=method, args = (args if args else ()))
thread.start()
while thread.is_alive():
self.progress.pulse()
while gtk.events_pending():
gtk.main_iteration(False)
time.sleep(0.1)
if self.process_error is not None:
raise Exception(self.process_error)
def run_command_in_thread(self, *args):
self.run_in_thread(self.run_command, args)
def configure_qubes(self):
self.show_stage('Executing qubes configuration')
for state in QubesChoice.get_states():
self.run_command_in_thread(['qubesctl', 'top.enable', state,
'saltenv=dom0', '-l', 'quiet', '--out', 'quiet'])
self.run_command_in_thread(['qubesctl', 'state.highstate'])
def configure_default_template(self):
self.show_stage('Setting default template')
self.run_command_in_thread(['/usr/bin/qubes-prefs', '--set',
'default-template', self.default_template])
def configure_network(self):
self.show_stage('Setting up networking')
self.run_in_thread(self.do_configure_network)
def configure_default_dvm(self):
self.show_stage(_("Creating default DisposableVM"))
self.run_in_thread(self.do_configure_default_dvm)
def do_configure_default_dvm(self):
try:
self.run_command(['su', '-c', '/usr/bin/qvm-create-default-dvm --default-template --default-script', self.qubes_user])
except:
# Kill DispVM template if still running
# Do not use self.run_command to not clobber process output
subprocess.call(['qvm-kill', '{}-dvm'.format(self.default_template)])
raise
def find_net_devices(self):
p = subprocess.Popen (["/sbin/lspci", "-mm", "-n"], stdout=subprocess.PIPE)
result = p.communicate()
retcode = p.returncode
if (retcode != 0):
if retcode != 0:
print "ERROR when executing lspci!"
raise IOError
@ -226,52 +261,18 @@ class moduleClass(Module):
assert dev_bdf is not None
net_devices.add (dev_bdf)
return net_devices
return net_devices
def do_configure_network(self):
self.run_command(['/usr/bin/qvm-prefs', '--force-root', '--set', 'sys-firewall', 'netvm', 'sys-net'])
self.run_command(['/usr/bin/qubes-prefs', '--set', 'default-netvm', 'sys-firewall'])
def do_create_netvm(self):
self.run_command(['su', '-c', '/usr/bin/qvm-create --net --label red %s' % self.netvm_name, self.qubes_user])
for dev in self.find_net_devices():
self.run_command(['/usr/bin/qvm-pci', '-a', self.netvm_name, dev])
self.run_command(['/usr/bin/qvm-pci', '-a', 'sys-net', dev])
def do_create_fwvm(self):
self.run_command(['su', '-c', '/usr/bin/qvm-create --proxy --label green %s' % self.fwvm_name, '-', self.qubes_user])
def do_create_dvm(self):
try:
self.run_command(['su', '-c', '/usr/bin/qvm-create-default-dvm --default-template --default-script', self.qubes_user])
except:
# Kill DispVM template if still running
# Do not use self.run_command to not clobber process output
subprocess.call(['qvm-kill', '{}-dvm'.format(self.default_template)])
raise
def do_set_netvm_networking(self):
self.run_command(['/usr/bin/qvm-prefs', '--force-root', '--set', self.fwvm_name, 'netvm', self.netvm_name])
self.run_command(['/usr/bin/qubes-prefs', '--set', 'default-netvm', self.fwvm_name])
def do_set_dom0_networking(self):
self.run_command(['/usr/bin/qubes-prefs', '--set', 'default-netvm', 'dom0'])
def do_start_networking(self):
self.run_command(['/usr/sbin/service', 'qubes-netvm', 'start'])
def do_configure_template(self, template):
self.run_command(['qvm-start', '--no-guid', template])
# Copy timezone setting from Dom0 to template
self.run_command(['qvm-run', '--nogui', '--pass-io',
'-u', 'root', template, 'cat > /etc/locale.conf'],
stdin=open('/etc/locale.conf', 'r'))
self.run_command(['su', '-c',
'qvm-sync-appmenus {}'.format(template),
'-', self.qubes_user])
self.run_command(['qvm-shutdown', '--wait', template])
def do_create_appvms(self):
self.run_command(['su', '-c', '/usr/bin/qvm-create work --label green', '-', self.qubes_user])
self.run_command(['su', '-c', '/usr/bin/qvm-create banking --label green', '-', self.qubes_user])
self.run_command(['su', '-c', '/usr/bin/qvm-create personal --label yellow', '-', self.qubes_user])
self.run_command(['su', '-c', '/usr/bin/qvm-create untrusted --label red', '-', self.qubes_user])
def createScreen(self):
self.vbox = gtk.VBox(spacing=5)
@ -286,19 +287,44 @@ class moduleClass(Module):
label.set_size_request(500, -1)
self.vbox.pack_start(label, False, True, padding=20)
self.radio_servicevms_and_appvms = gtk.RadioButton(None, _("Create default service VMs, and pre-defined AppVMs (work, banking, personal, untrusted)"))
self.vbox.pack_start(self.radio_servicevms_and_appvms, False, True)
self.choice_network = QubesChoice(
_('Create default system qubes (sys-net, sys-firewall)'),
('qvm.sys-net', 'qvm.sys-firewall'))
self.radio_onlyservicevms = gtk.RadioButton(self.radio_servicevms_and_appvms, _("Just create default service VMs"))
self.vbox.pack_start(self.radio_onlyservicevms, False, True)
self.choice_default = QubesChoice(
_('Create default application qubes '
'(personal, work, untrusted, vault)'),
('qvm.personal', 'qvm.work', 'qvm.untrusted', 'qvm.vault'),
depend=self.choice_network)
self.radio_dontdoanything = gtk.RadioButton(self.radio_servicevms_and_appvms, _("Do not create any VMs right now (not recommended, for advanced users only)"))
self.vbox.pack_start(self.radio_dontdoanything, False, True)
self.choice_whonix = QubesChoice(
_('Create Whonix Gateway and Workstation qubes '
'(sys-whonix, anon-whonix)'),
('qvm.sys-whonix', 'qvm.anon-whonix'),
depend=self.choice_network,
extra_check=lambda: is_package_installed('qubes-template-whonix-gw')
and is_package_installed('qubes-template-whonix-ws'))
self.check_advanced = gtk.CheckButton(
_('Do not configure anything (for advanced users)'))
self.check_advanced.connect('toggled',
QubesChoice.on_check_advanced_toggled)
for choice in QubesChoice.instances:
self.vbox.pack_start(choice.widget, False, True)
#self.vbox.pack_start(gtk.HSeparator())
self.vbox.pack_end(self.check_advanced, False, True)
self.progress = None
def initializeUI(self):
self.radio_servicevms_and_appvms.set_active(True)
self.check_advanced.set_active(False)
self.choice_network.widget.set_active(True)
self.choice_default.widget.set_active(True)
self.choice_whonix.widget.set_active(False)
self.qubes_gid = grp.getgrnam('qubes').gr_gid
self.stage = "Initialization"
self.process_error = None