Basically, take this and paste it into dom0 then make it executable.
There'a also a handy test function. All credit goes to Andrea Micheloni.
Anyone have similarly handy modifications/scripts?
https://m7i.org/tips/qubes-update-all-templates/
#!/usr/bin/python2
#
# Copyright (c) Andrea Micheloni 2018
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
import sys, os, time, subprocess, unittest
from Queue import Queue
from threading import Thread, current_thread
class QubesVm:
def __init__(self, name, template, is_running):
self.name = name
self.template = template
self._is_running = is_running
self._is_updated = False
def is_running(self):
return self._is_running
def is_template(self):
return (self.template is None)
def is_updated(self):
return self._is_updated
def run(self, command, autostart = False, verbose = True, user = None,
notify_function = None,
passio = False, localcmd = None, gui = True, filter_esc = True
):
qvm_command = ['qvm-run']
if autostart:
qvm_command += ['--autostart']
else:
qvm_command += ['--no-autostart']
if not verbose:
qvm_command += ['--quiet']
if user:
qvm_command += ['--user=%s' % user]
if gui:
qvm_command += ['--gui']
else:
qvm_command += ['--no-gui']
qvm_command += [self.name, command]
subprocess.call(qvm_command)
class QubesHandler:
def __init__(self):
raw_result = self._get_raw_qubes_string()
self._qubes = {}
for qube_string in raw_result.split("\n"):
if len(qube_string)>0:
qube_properties = qube_string.split("|")
qube_class = qube_properties[0]
qube_name = qube_properties[1]
qube_template = None;
if qube_properties[2] != '-':
qube_template = qube_properties[2]
qube_running = True
if qube_properties[3] == 'Halted':
qube_running = False
if qube_class != "AdminVM":
new_qube = self._create_qube(qube_name, qube_template,
qube_running)
self._qubes[qube_name] = new_qube
def _create_qube(self, qube_name, qube_template, qube_running):
return QubesVm(qube_name, qube_template, qube_running)
def _get_raw_qubes_string(self):
return subprocess.check_output(['qvm-ls', '--fields',
'CLASS,NAME,TEMPLATE,STATE', '--raw-data'])
def _get_template_dictionary(self, template_vm, rank):
return {'name': template_vm.name,
'qvm': template_vm,
'rank': rank}
def _get_ranked_templates(self):
templates_ranked = {}
for vm in self._qubes.values():
if vm.is_template():
if not vm.name in templates_ranked:
templates_ranked[vm.name] = self._get_template_dictionary(vm,
0)
else:
if vm.template:
if vm.is_running():
rank = 3
else:
rank = 1
if not vm.template in templates_ranked:
templates_ranked[vm.template] = \
self._get_template_dictionary(self._qubes[vm.template],
rank)
else:
templates_ranked[vm.template]['rank'] += rank
return templates_ranked
def get_templates(self):
templates_ranked = self._get_ranked_templates()
return [templates_ranked[template] for template in sorted(templates_ranked,
key=lambda name: templates_ranked[name][
'rank'],
reverse = True)]
def update_template(self, template, print_function):
print_function('Updating template...')
try:
template['qvm'].run('touch /tmp/update-all; chmod go+r
/tmp/update-all; { if [ -f /usr/bin/snap ' +
']; then /usr/bin/snap refresh || exit; fi;
if [ -f /usr/bin/apt-get ' +
']; then /usr/bin/apt-get update &&
/usr/bin/apt-get -y upgrade </dev/null ' +
'&& /usr/bin/apt-get -y autoremove &&
/usr/bin/apt-get autoclean && ' +
'/sbin/poweroff; else /usr/bin/dnf -y
upgrade </dev/null && /usr/bin/dnf ' +
'clean all && /usr/sbin/poweroff; fi; } 2>&1
| tee -a /tmp/update-all',
autostart = True, verbose = False, user =
'root', notify_function = None,
passio = False, localcmd = None, gui = False,
filter_esc = True )
print_function('Update complete.')
except:
print_function('Update failed.', error=True)
raise
class UpdateWorker(Thread):
def __init__(self, name, queue, qubes_handler):
Thread.__init__(self)
self.daemon = True
self.name = name
self._queue = queue
self._qubes_handler = qubes_handler
self._template = None
def _print_function(self, text, error=False):
if error:
prefix = 'ERR'
else:
prefix = 'INF'
prefix += ' ['+self.name+' '+self._template['name']+'] '
print prefix + text
def run(self):
while True:
self._template = {'name':'None'}
self._template = self._queue.get()
try:
self._qubes_handler.update_template(self._template, self
._print_function)
self._template = {'name':'None'}
finally:
self._template = {'name':'None'}
self._queue.task_done()
self._template = {'name':'None'}
class UpdateHandler:
def __init__(self):
self._queue = Queue()
def update_templates(self, thread_number):
qubes_handler = self._get_qubes_handler()
print 'INF Updating templates: ', [template['name'] for template in
qubes_handler.get_templates()]
workers = [self._get_worker('w'+str(number), qubes_handler) for number
in range(thread_number)]
for worker in workers:
worker.start()
for template in qubes_handler.get_templates():
self._queue.put(template, block = True)
self._queue.join()
def _get_worker(self, name, qubes_handler):
return UpdateWorker(name, self._queue, qubes_handler)
def _get_qubes_handler(self):
return QubesHandler()
class MockQubesVm(QubesVm):
def __init__(self, name, template, is_running):
QubesVm.__init__(self, name, template, is_running)
self.name = name
self.template = template
self._is_running = is_running
self._is_updated = False
def is_running(self):
return self._is_running
def is_template(self):
return (self.template is None)
def is_updated(self):
return self._is_updated
def __repr__(self):
return "MockQubesVm(%s,%s,%s)" % (self.name, self.template, self
._is_running)
def __eq__(self, other):
return repr(self) == repr(other)
def run(self, command, autostart = False, verbose = True, user = None,
notify_function = None,
passio = False, localcmd = None, gui = True, filter_esc = True
):
if (autostart):
self._is_running = True
if (user == 'root' and self._is_running):
self._is_updated = True
self._is_running = False
class MockQubesHandler(QubesHandler, unittest.TestCase):
def __init__(self, *args, **kwargs):
if (args):
unittest.TestCase.__init__(self, *args, **kwargs)
self.maxDiff=None
self._template_f = MockQubesVm('fedora-24', None, False)
self._template_fm = MockQubesVm('fedora-24-minimal', None, True)
self._template_d = MockQubesVm('debian-8', None, False)
self._template_ws = MockQubesVm('whonix-ws', None, True)
self._template_wg = MockQubesVm('whonix-wg', None, True)
self._ranked_f = {'name': 'fedora-24', 'qvm': self._template_f,
'rank': 9}
self._ranked_fm = {'name': 'fedora-24-minimal', 'qvm': self._template_fm,
'rank': 4}
self._ranked_d = {'name': 'debian-8', 'qvm': self._template_d,
'rank': 5}
self._ranked_ws = {'name': 'whonix-ws', 'qvm': self._template_ws,
'rank': 1}
self._ranked_wg = {'name': 'whonix-wg', 'qvm': self._template_wg,
'rank': 3}
self._expected_ranked = {
'fedora-24': self._ranked_f,
'fedora-24-minimal': self._ranked_fm,
'debian-8': self._ranked_d,
'whonix-ws': self._ranked_ws,
'whonix-wg': self._ranked_wg }
self._expected = [ self._ranked_f, self._ranked_d, self._ranked_fm,
self._ranked_wg, self._ranked_ws]
QubesHandler.__init__(self)
def _create_qube(self, qube_name, qube_template, qube_running):
return MockQubesVm(qube_name, qube_template, qube_running)
def _get_raw_qubes_string(self):
return 'AppVM|sys-net|fedora-24|Running\n' + \
'AppVM|sys-whonix|whonix-wg|Running\n' + \
'AppVM|personal|debian-8|Halted\n' + \
'AppVM|sys-firewall|fedora-24-minimal|Running\n' + \
'TemplateVM|debian-8|-|Halted\n' + \
'AdminVM|dom0|-|Running\n' + \
'AppVM|gnupg|fedora-24-minimal|Halted\n' + \
'AppVM|disp5|fedora-24|Suspended\n' + \
'AppVM|anonymous|whonix-ws|Halted\n' + \
'TemplateVM|fedora-24-minimal|-|True\n' + \
'TemplateVM|whonix-ws|-|Running\n' + \
'TemplateVM|whonix-wg|-|Running\n' + \
'AppVM|work|debian-8|Suspended\n' + \
'AppVM|development|debian-8|Halted\n' + \
'AppVM|disp2|fedora-24|Running\n' + \
'TemplateVM|fedora-24|-|Halted\n'
def get_updated_status(self):
return [self._qubes[self._template_f.name].is_updated(),
self._qubes[self._template_fm.name].is_updated(),
self._qubes[self._template_d.name].is_updated(),
self._qubes[self._template_ws.name].is_updated(),
self._qubes[self._template_wg.name].is_updated()]
def test_get_template_dictionary(self):
self.assert_get_template_dictionary(self._template_f)
self.assert_get_template_dictionary(self._template_fm)
self.assert_get_template_dictionary(self._template_d)
self.assert_get_template_dictionary(self._template_ws)
self.assert_get_template_dictionary(self._template_wg)
def assert_get_template_dictionary(self, template):
found = self._get_template_dictionary(template, len(template.name))
self.assertEquals(3, len(found.keys()))
self.assertEquals(template, found['qvm'])
self.assertEquals(len(template.name), found['rank'])
self.assertEquals(template.name, found['name'])
def test_get_template_dictionary_custom(self):
template = MockQubesVm('test-template', None, False)
found = self._get_template_dictionary(template, 0)
self.assertEquals(3, len(found.keys()))
self.assertEquals(template, found['qvm'])
self.assertEquals(0, found['rank'])
self.assertEquals('test-template', found['name'])
def test_get_templates(self):
found = self.get_templates()
self.assertEquals(self._expected, found)
def test_get_ranked_templates(self):
found = self._get_ranked_templates()
self.assertEquals(self._expected_ranked, found)
class MockUpdateWorker(UpdateWorker):
def _print_function(self, text, error=False):
pass
class MockUpdateHandler(UpdateHandler, unittest.TestCase):
def __init__(self, *args, **kwargs):
UpdateHandler.__init__(self)
unittest.TestCase.__init__(self, *args, **kwargs)
self.generate_qubes_handler()
def _get_worker(self, name, qubes_handler):
return MockUpdateWorker(name, self._queue, qubes_handler)
def _get_qubes_handler(self):
return self._qubes_handler
def generate_qubes_handler(self):
self._qubes_handler = MockQubesHandler()
def test_update_all(self):
for number in range(1,10):
self.assert_update_all(number)
self.generate_qubes_handler()
def assert_update_all(self, thread_number):
self.assertEquals([False]*5 , self
._qubes_handler.get_updated_status())
self.update_templates(thread_number)
self.assertEquals([True]*5 , self
._qubes_handler.get_updated_status())
if __name__ == '__main__':
if len(sys.argv) == 1:
UpdateHandler().update_templates(3)
elif len(sys.argv) >2 or sys.argv[1] != 'test':
print "ERR Usage: dom0-update-all [test]"
else:
unittest.main(argv=sys.argv[:1])
IIRC there is an option somewhere in 'qubesctl' for parallel template
updates.
You can check out my github for some interesting stuff. The
'Qubes-scripts' project has a (serial) template updater that lets you
select by certain criteria. It could be parallelized pretty easily.
Since you have a lot of templates+standalones, the 'findpref' tool might
also be of interest. It can bulk search/replace VM prefs, such as
changing all the VMs that are using 'sys-vpn1' to use 'sys-vpn3'
instead, or change VMs to use a different template.
I also wrote 'wyng-backup', a fast LVM incremental backup tool that only
scans where LVM reports new activity.
Finally, there is a VPN tool and one to enhance VM internal security.
--
Chris Laprise, tas...@posteo.net
https://github.com/tasket
https://twitter.com/ttaskett
PGP: BEE2 20C5 356E 764A 73EB 4AB3 1DC4 D106 F07F 1886
On that topic, I'm having huge difficulties making it so starting a qube, say app-firefox, automatically starts a program, like firefox. I've tried rc.local but that's pre-boot, before X11. Others have suggested something related to /etc/config or something, but that involves fiddling with templates and implies an ungodly amount of templates. Would you happen to have any suggestions?
You can check out my github for some interesting stuff. The
'Qubes-scripts' project has a (serial) template updater that lets you
select by certain criteria. It could be parallelized pretty easily.
[...]
Finally, there is a VPN tool and one to enhance VM internal security.
--
Chris Laprise, tas...@posteo.net
https://github.com/tasket
https://twitter.com/ttaskett
PGP: BEE2 20C5 356E 764A 73EB 4AB3 1DC4 D106 F07F 1886
I tested your halt-vm-by-window and system-stats-xen and found them very useful. I also tried your qubes4-multi-update but ran into three issues: one is that it relies on curl, which my Fedora minimal wasn't happy about; another is that it [Y/n] prompts me for upgrades, which it shouldn't do, according to the script; the last is that it attempts to update mirage firewall standalones and when it fails, the whole process stops.
'curl' would only be used in a Whonix template. This is to signal Qubes'
proxy to start the Tor-based updateVM as soon as possible. It should not
try to run curl in a Fedora or regular Debian template.
To suppress interactive prompts, you need to run the script with '-u' or
'--unattended'.
Yes, vm-boot-protect does lock down that dir, along with other startup
files and dirs in /home. The way it does this is with the 'immutable'
flag. To change it (re)start the VM and do:
sudo chattr -i -R .config/autostart
Then change what you need to in that path and restart the VM. During the
startup process the dir and its contents will be automatically made
immutable again.
BTW, I think the appVM is the right place to make the .config/autostart
change if the custom .desktop file is being applied on a per-VM basis.
Yes, the requirements to get it running keep changing. Right now the
easiest way is to install 'kernel-latest-qubes-vm' from dom0 to get a
5.x kernel for VMs (the 5.x kernels have wg module included), then
install the wireguard-tools package without dependencies in your template.
I'll be switching to wireguard in the next few weeks so I'll be updating
the wiki then.
1. Install the 'kernel-latest-qubes-vm' package in dom0. This will
provide a 5.x kernel with wireguard module built-in. Set your VPN VM to
use this kernel.
2. Install only the 'wireguard-tools' package (from testing) in Debian
10. Otherwise, there may be a conflict between the built-in and DKMS
modules.
3. Given the above, it may now be possible to skip using HVM mode
altogether.
Actually instead of parallel updates (assuming limited bandwidth) I'd
vote for a more verbose progress indicator (in the graphical update app):
Currently the VMs start, update starts, and then ...long time
nothing..., then the list of packages updated.
Regards,
Ulrich
The security isnt to be found at the proxy level, but at the package
management level. It's there that verification is (and should be) done.
-- I'm not unman, but I just checked the repo data and it appears they use sha256
I hate to break that feeling, but Fedora is unique in that it doesn't
sign its repo metadata, and sadly that is what matters. They put a
bandaid on it by fetching more hashes via https... so the update
security in Fedora is based on the strength of https. That is bad, as
https can be subverted by resourceful attackers.
https://bugzilla.redhat.com/show_bug.cgi?id=1130491
What this potentially allows is an attacker to blind Fedora systems to
specific package updates, where the systems appear to retrieve updates
normally without the users being aware that particular packages with
known vulnerabilities have been held back.
--
Chris Laprise, tas...@posteo.net
https://github.com/tasket
https://twitter.com/ttaskett
PGP: BEE2 20C5 356E 764A 73EB 4AB3 1DC4 D106 F07F 1886
Hi all,
Every time I use qubes-dom0-update in a fresh installation (which I've done around ten times now), I get strange outputs where the repositories aren't shown being queried but the update proceeds. It looks something like this:
error:could not delete old database at /var/lib/qubes/dom0-updates/home/user/.rpmdbold.965
https://mirrors.phx.ms/qubes/repo/yum/r4.0/current/dom0/fc25/repodata/repomd.xml:[Errno 14]curl#6-"Could not resolve host:mirror.phx.ms"
Trying other mirror.
https://mirror.linux.pizza/qubes-os.org/repo/yum/r4.0/current/dom0/fc25/repodata/repomd.xml:[Errno14]HTTPS Error 404 -Not Found
Trying other mirror.
https://mirror.linux.pizza/qubes-os.org/repo/yum/r4.0/templates-til/repodata/repomd.xml:[Errno 14] HTTPS Error 404 - Not Found
Trying other mirror.
No Match for argument
No Match for argument
No Match for argument
No Match for argument
No Match for argument
No Match for argument
No Match for argument
No Match for argument
-->Running transaction check
--->Package kernel[...] will be installed
[...]
--->Finished Dependency Resolution
[Starts downloading]
This is consistent even when updating over tor, and has been bugging me. Does anyone else see this when they first update dom0?
I hate to break that feeling, but Fedora is unique in that it doesn't
sign its repo metadata, and sadly that is what matters. They put a
bandaid on it by fetching more hashes via https... so the update
security in Fedora is based on the strength of https. That is bad, as
https can be subverted by resourceful attackers.
IIRC that setting refers to checking packages, not the repomd.xml files.
That's why an attacker can't replace packages with their own versions;
they have to manipulate the metadata to hold back packages from
receiving updates.
--
Chris Laprise, tas...@posteo.net
https://github.com/tasket
https://twitter.com/ttaskett
PGP: BEE2 20C5 356E 764A 73EB 4AB3 1DC4 D106 F07F 1886
Yes. Note that Qubes Security Bulletins are issued for vulns that affect
dom0 and they reference the package versions that contain the patches.
For example:
https://groups.google.com/d/msgid/qubes-users/34eddc9a-300c-743c-cb12-acc677f5784f%40qubes-os.org
However, most vulns that affect templates are not addressed by QSBs
because they're not Qubes-specific. That's one reason to avoid Fedora
templates in general.
--
Chris Laprise, tas...@posteo.net
https://github.com/tasket
https://twitter.com/ttaskett
PGP: BEE2 20C5 356E 764A 73EB 4AB3 1DC4 D106 F07F 1886