dom0+vm: Tools for downloading dom0 update by VM (#198)

Mainly 4 parts:
 - scripts for providing rpmdb and yum repos to VM (choosen by qvm-set-updatevm)
 - VM script for downloading updates (qubes_download_dom0_updates.sh)
 - qfile-dom0-unpacker which receive updates, check signatures and place its in dom0 local yum repo
 - qvm-dom0-upgrade which calls all of above and after all yum gpk-update-viewer

Besides qvm-dom0-upgrade, updates are checked every 6h and user is prompted if
want to download it. At dom0 side gpk-update-icon (disabled yet) should notice
new updates in "local" repo.
This commit is contained in:
Marek Marczykowski 2011-06-22 00:44:48 +02:00
parent b2a0a09168
commit d9d7a69c27
20 changed files with 358 additions and 3 deletions

View File

@ -5,9 +5,9 @@ dvm_file_editor: dvm_file_editor.o ../common/ioall.o
$(CC) -pie -g -o $@ $^
qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o
$(CC) -pie -g -o $@ $^
qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o crc32.o
qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o ../common/copy_file.o ../common/crc32.o
$(CC) -pie -g -o $@ $^
qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o crc32.o
qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o ../common/copy_file.o ../common/unpack.o ../common/crc32.o
$(CC) -pie -g -o $@ $^
clean:

View File

@ -0,0 +1,51 @@
#!/bin/bash
DOM0_UPDATES_DIR=/var/lib/qubes/dom0-updates
DOIT=0
GUI=1
while [ -n "$1" ]; do
if [ "x--doit" = "x$1" ]; then
DOIT=1
elif [ "x--nogui" = "x$1" ]; then
GUI=0
fi
shift
done
if ! [ -d "$DOM0_UPDATES_DIR" ]; then
echo "Dom0 updates dir does not exists: $DOM0_UPDATES_DIR"
exit 1
fi
mkdir -p $DOM0_UPDATES_DIR/etc
cp /etc/yum.conf $DOM0_UPDATES_DIR/etc/
echo "Checking for updates..."
PKGLIST=`yum --installroot $DOM0_UPDATES_DIR check-update -q | cut -f 1 -d ' '`
if [ -z $PKGLIST ]; then
# No new updates
exit 0
fi
if [ "$DOIT" != "1" ]; then
zenity --question --title="Qubes Dom0 updates" \
--text="Updates for dom0 available. Do you want to download its now?" || exit 0
fi
mkdir -p "$DOM0_UPDATES_DIR/packages"
set -e
if [ "$GUI" = 1 ]; then
( echo "1"
yumdownloader --destdir "$DOM0_UPDATES_DIR/packages" --installroot "$DOM0_UPDATES_DIR" $PKGLIST
echo 100 ) | zenity --progress --pulsate --auto-close --auto-kill \
--text="Downloading updates for Dom0, please wait..." --title="Qubes Dom0 updates"
else
yumdownloader --destdir "$DOM0_UPDATES_DIR/packages" --installroot "$DOM0_UPDATES_DIR" $PKGLIST
fi
# qvm-copy-to-vm works only from user
su -c "qvm-copy-to-vm @dom0updates $DOM0_UPDATES_DIR/packages/*.rpm" user

1
dom0/aux-tools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
qfile-dom0-unpacker

4
dom0/aux-tools/Makefile Normal file
View File

@ -0,0 +1,4 @@
CC=gcc
CFLAGS=-g -Wall -I../../common -fPIC -pie
qfile-dom0-unpacker: qfile-dom0-unpacker.o ../../common/ioall.o ../../common/gui-fatal.o ../../common/copy_file.o ../../common/unpack.o ../../common/crc32.o
$(CC) -pie -g -o $@ $^

View File

@ -0,0 +1,86 @@
#define _GNU_SOURCE
#include <ioall.h>
#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <gui-fatal.h>
#include <errno.h>
#include "filecopy.h"
int prepare_creds_return_uid(char *username)
{
struct passwd *pwd;
pwd = getpwnam(username);
if (!pwd) {
perror("getpwnam");
exit(1);
}
setenv("HOME", pwd->pw_dir, 1);
setenv("USER", username, 1);
setgid(pwd->pw_gid);
initgroups(username, pwd->pw_gid);
setfsuid(pwd->pw_uid);
return pwd->pw_uid;
}
void wait_for_child(int statusfd)
{
int status;
if (read(statusfd, &status, sizeof status)!=sizeof status)
gui_fatal("File copy error: Internal error reading status from unpacker");
errno = status;
switch (status) {
case LEGAL_EOF: break;
case 0: gui_fatal("File copy: Connection terminated unexpectedly"); break;
case EINVAL: gui_fatal("File copy: Corrupted data from packer"); break;
case EEXIST: gui_fatal("File copy: not overwriting existing file. Clean ~/incoming, and retry copy"); break;
default: gui_fatal("File copy");
}
}
extern void do_unpack(int);
int main(int argc, char ** argv)
{
char *incoming_dir;
int pipefds[2];
int uid;
if (argc < 3) {
fprintf(stderr, "Invalid parameters, usage: %s user dir\n", argv[0]);
exit(1);
}
pipe(pipefds);
uid = prepare_creds_return_uid(argv[1]);
incoming_dir = argv[2];
mkdir(incoming_dir, 0700);
if (chdir(incoming_dir))
gui_fatal("Error chdir to %s", incoming_dir);
switch (fork()) {
case -1:
perror("fork");
exit(1);
case 0:
if (chroot(incoming_dir)) //impossible
gui_fatal("Error chroot to %s", incoming_dir);
setuid(uid);
close(pipefds[0]);
do_unpack(pipefds[1]);
exit(0);
default:;
}
setuid(uid);
close(pipefds[1]);
wait_for_child(pipefds[0]);
return 0;
}

View File

@ -0,0 +1,10 @@
#!/bin/sh
UPDATEVM=`qvm-get-updatevm`
if [ -n "$UPDATEVM" ]; then
qvm-run -u root --pass_io --localcmd='tar c /var/lib/rpm /etc/yum.repos.d' "$UPDATEVM" 'tar x -C /var/lib/qubes/dom0-updates'
fi
# Ignore errors (eg VM not running)
exit 0

View File

@ -0,0 +1 @@
*:any:/usr/lib/qubes/sync_rpmdb_updatevm.sh

5
dom0/qubes-cached.repo Normal file
View File

@ -0,0 +1,5 @@
[qubes-dom0-cached]
name = Qubes OS Repository for Dom0
baseurl = file:///var/lib/qubes/updates
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-1-primary
gpgcheck = 1

View File

@ -74,6 +74,8 @@ default_firewall_conf_file = "firewall.xml"
default_memory = 400
default_servicevm_vcpus = 1
dom0_update_check_interval = 6*3600
# do not allow to start a new AppVM if Dom0 mem was to be less than this
dom0_min_memory = 700*1024*1024
@ -937,6 +939,13 @@ class QubesVm(object):
print "--> Preparing config template for DispVM"
self.create_config_file(file_path = self.dir_path + '/dvm.conf', prepare_dvm = True)
if qvm_collection.updatevm_qid == self.qid:
# Sync RPMDB
subprocess.call(["/usr/lib/qubes/sync_rpmdb_updatevm.sh"])
# Start polling
subprocess.call([qrexec_client_path, '-d', xid, '-e',
"while true; do sleep %d; /usr/lib/qubes/qubes_download_dom0_updates.sh; done" % dom0_update_check_interval])
# perhaps we should move it before unpause and fork?
# FIXME: this uses obsolete xm api
if debug_console:
@ -1609,6 +1618,7 @@ class QubesVmCollection(dict):
self.default_netvm_qid = None
self.default_fw_netvm_qid = None
self.default_template_qid = None
self.updatevm_qid = None
self.qubes_store_filename = store_filename
def values(self):
@ -1769,6 +1779,15 @@ class QubesVmCollection(dict):
else:
return self[self.default_fw_netvm_qid]
def set_updatevm_vm(self, vm):
self.updatevm_qid = vm.qid
def get_updatevm_vm(self):
if self.updatevm_qid is None:
return None
else:
return self[self.updatevm_qid]
def get_vm_by_name(self, name):
for vm in self.values():
if (vm.name == name):
@ -1872,7 +1891,10 @@ class QubesVmCollection(dict):
if self.default_netvm_qid is not None else "None",
default_fw_netvm=str(self.default_fw_netvm_qid) \
if self.default_fw_netvm_qid is not None else "None"
if self.default_fw_netvm_qid is not None else "None",
updatevm=str(self.updatevm_qid) \
if self.updatevm_qid is not None else "None"
)
for vm in self.values():
@ -2002,6 +2024,13 @@ class QubesVmCollection(dict):
if default_fw_netvm != "None" else None
#assert self.default_netvm_qid is not None
updatevm = element.get("updatevm")
if updatevm is not None:
self.updatevm_qid = int(updatevm) \
if updatevm != "None" else None
#assert self.default_netvm_qid is not None
# Then, read in the TemplateVMs, because a reference to template VM
# is needed to create each AppVM
for element in tree.findall("QubesTemplateVm"):

20
dom0/qvm-tools/qvm-dom0-upgrade Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
UPDATEVM=`qvm-get-updatevm`
if [ -z "$UPDATEVM" ]; then
echo "UpdateVM not set, exiting"
exit 1
fi
echo "Checking for dom0 updates"
# Start VM if not running already
qvm-run -a $UPDATEVM true || exit 1
/usr/lib/qubes/sync_rpmdb_updatevm.sh || exit 1
qvm-run -u root --pass_io $UPDATEVM "/usr/lib/qubes/qubes_download_dom0_updates.sh --doit $@" || exit 1
yum check-update
if [ $? -ne 100 ]; then
exit 0
fi
gpk-update-viewer

39
dom0/qvm-tools/qvm-get-updatevm Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/python2.6
#
# 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 optparse import OptionParser;
def main():
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
updatevm = qvm_collection.get_updatevm_vm()
if updatevm is None:
print ""
else:
print updatevm.name
main()

46
dom0/qvm-tools/qvm-set-updatevm Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/python2.6
#
# 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 optparse import OptionParser;
def main():
usage = "usage: %prog <vm-name>"
parser = OptionParser (usage)
(options, args) = parser.parse_args ()
if (len (args) != 1):
parser.error ("Missing argument!")
vmname = args[0]
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
qvm_collection.load()
vm = qvm_collection.get_vm_by_name(vmname)
if vm is None or vm.qid not in qvm_collection:
print "A VM with the name '{0}' does not exist in the system.".format(vmname)
exit(1)
qvm_collection.set_updatevm_vm(vm)
qvm_collection.save()
qvm_collection.unlock_db()
main()

View File

@ -22,8 +22,13 @@
import os
import sys
import subprocess
import shutil
import glob
from qubes.qubes import QubesVmCollection
updates_dir = "/var/lib/qubes/updates"
updates_rpm_dir = updates_dir + "/rpm"
def is_copy_allowed(vm):
# if vm.copy_allowed:
# return True
@ -33,6 +38,36 @@ def is_copy_allowed(vm):
retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'File transfer confirmation'])
return retcode == 0
def dom0updates_fatal(msg):
print >> sys.stderr, msg
shutil.rmtree(updates_rpm_dir)
exit(1)
def handle_dom0updates(updatevm):
source=os.getenv("QREXEC_REMOTE_DOMAIN")
if source != updatevm.name:
print >> sys.stderr, 'Domain ' + source + ' not allowed to send dom0 updates'
exit(1)
# Clean old packages
if os.path.exists(updates_rpm_dir):
shutil.rmtree(updates_rpm_dir)
subprocess.check_call(["/usr/lib/qubes/qfile-dom0-unpacker", os.getlogin(), updates_rpm_dir])
# Verify received files
for f in os.listdir(updates_rpm_dir):
if glob.fnmatch.fnmatch(f, "*.rpm"):
p = subprocess.Popen (["/bin/rpm", "-K", updates_rpm_dir + "/" + f],
stdout=subprocess.PIPE)
output = p.communicate()[0]
if p.returncode != 0:
dom0updates_fatal('Error while verifing %s signature: %s' % (f, output))
if output.find("pgp") < 0:
dom0updates_fatal('Domain ' + source + ' sent not signed rpm: ' + f)
else:
dom0updates_fatal('Domain ' + source + ' sent unexpected file: ' + f)
# After updates received - create repo metadata
subprocess.check_call(["/usr/bin/createrepo", "-q", "/var/lib/qubes/updates"])
exit(0)
def main():
FILECOPY_VMNAME_SIZE = 32
blob=os.read(0, FILECOPY_VMNAME_SIZE)
@ -42,6 +77,11 @@ def main():
qvm_collection.lock_db_for_reading()
qvm_collection.load()
qvm_collection.unlock_db()
if vmname == '@dom0updates':
updatevm = qvm_collection.get_updatevm_vm()
handle_dom0updates(updatevm)
# handle_dom0updates never returns
vm = qvm_collection.get_vm_by_name(vmname)
# we do not want to flood dom0 with error windows; so just log to stderr

View File

@ -79,10 +79,12 @@ mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d
cp qubes_network.rules $RPM_BUILD_ROOT/etc/udev/rules.d/
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes/
cp setup_ip $RPM_BUILD_ROOT/usr/lib/qubes/
cp qubes_download_dom0_updates.sh $RPM_BUILD_ROOT/usr/lib/qubes/
mkdir -p $RPM_BUILD_ROOT/etc/yum/post-actions
cp qubes_trigger_sync_appmenus.action $RPM_BUILD_ROOT/etc/yum/post-actions/
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes
cp qubes_trigger_sync_appmenus.sh $RPM_BUILD_ROOT/usr/lib/qubes/
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/dom0-updates
%triggerin -- initscripts
cp /var/lib/qubes/serial.conf /etc/init/serial.conf
@ -233,3 +235,4 @@ rm -rf $RPM_BUILD_ROOT
/usr/lib/qubes/setup_ip
/etc/yum/post-actions/qubes_trigger_sync_appmenus.action
/usr/lib/qubes/qubes_trigger_sync_appmenus.sh
/usr/lib/qubes/qubes_download_dom0_updates.sh

View File

@ -39,6 +39,7 @@ URL: http://www.qubes-os.org
BuildRequires: xen-devel
Requires: python, xen-runtime, pciutils, python-inotify, python-daemon, kernel-qubes-dom0
Conflicts: qubes-gui-dom0 < 1.1.13
Requires: yum-plugin-post-transaction-actions
Requires: NetworkManager >= 0.8.1-1
Requires: xen >= 4.1.0-2
%define _builddir %(pwd)/dom0
@ -50,6 +51,7 @@ The Qubes core files for installation on Dom0.
python -m compileall qvm-core qmemman
python -O -m compileall qvm-core qmemman
make -C restore
make -C aux-tools
make -C ../common
make -C ../vchan
make -C ../u2mfn
@ -89,10 +91,12 @@ cp aux-tools/convert_dirtemplate2vm.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp aux-tools/create_apps_for_appvm.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp aux-tools/remove_appvm_appmenus.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp aux-tools/reset_vm_configs.py $RPM_BUILD_ROOT/usr/lib/qubes
cp aux-tools/sync_rpmdb_updatevm.sh $RPM_BUILD_ROOT/usr/lib/qubes/
cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py
cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/
cp ../qrexec/qrexec_daemon $RPM_BUILD_ROOT/usr/lib/qubes/
cp ../qrexec/qrexec_client $RPM_BUILD_ROOT/usr/lib/qubes/
cp aux-tools/qfile-dom0-unpacker $RPM_BUILD_ROOT/usr/lib/qubes/
cp restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin
cp restore/xenstore-watch $RPM_BUILD_ROOT/usr/bin/xenstore-watch-qubes
@ -101,6 +105,12 @@ cp restore/qubes_prepare_saved_domain.sh $RPM_BUILD_ROOT/usr/lib/qubes
cp restore/qfile-daemon-dvm $RPM_BUILD_ROOT/usr/lib/qubes
cp restore/qfile-daemon $RPM_BUILD_ROOT/usr/lib/qubes
mkdir -p $RPM_BUILD_ROOT/etc/yum.real.repos.d
cp qubes-cached.repo $RPM_BUILD_ROOT/etc/yum.real.repos.d/
mkdir -p $RPM_BUILD_ROOT/etc/yum/post-actions
cp misc/qubes_sync_rpmdb_updatevm.action $RPM_BUILD_ROOT/etc/yum/post-actions/
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/vm-templates
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/appvms
@ -109,6 +119,8 @@ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/servicevms
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/backup
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/dvmdata
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/updates
mkdir -p $RPM_BUILD_ROOT/usr/share/qubes/icons
cp icons/*.png $RPM_BUILD_ROOT/usr/share/qubes/icons
cp misc/qubes-vm.directory.template $RPM_BUILD_ROOT/usr/share/qubes/
@ -158,6 +170,9 @@ fi
sed 's/^net.ipv4.ip_forward.*/net.ipv4.ip_forward = 1/' -i /etc/sysctl.conf
sed '/^reposdir=/d' -i /etc/yum.conf
echo reposdir=/etc/yum.real.repos.d >> /etc/yum.conf
chkconfig --add qubes_core || echo "WARNING: Cannot add service qubes_core!"
chkconfig --add qubes_netvm || echo "WARNING: Cannot add service qubes_netvm!"
chkconfig --add qubes_setupdvm || echo "WARNING: Cannot add service qubes_setupdvm!"
@ -267,12 +282,15 @@ fi
/usr/lib/qubes/meminfo-writer
/usr/lib/qubes/qfile-daemon-dvm*
/usr/lib/qubes/qfile-daemon
/usr/lib/qubes/sync_rpmdb_updatevm.sh
%attr(4750,root,qubes) /usr/lib/qubes/qfile-dom0-unpacker
%attr(770,root,qubes) %dir /var/lib/qubes
%attr(770,root,qubes) %dir /var/lib/qubes/vm-templates
%attr(770,root,qubes) %dir /var/lib/qubes/appvms
%attr(770,root,qubes) %dir /var/lib/qubes/servicevms
%attr(770,root,qubes) %dir /var/lib/qubes/backup
%attr(770,root,qubes) %dir /var/lib/qubes/dvmdata
%attr(770,root,qubes) %dir /var/lib/qubes/updates
%dir /usr/share/qubes/icons/*.png
/usr/share/qubes/qubes-vm.directory.template
/usr/share/qubes/qubes-templatevm.directory.template
@ -299,7 +317,9 @@ fi
%attr(770,root,qubes) %dir /var/run/qubes
%{_libdir}/libvchan.so
%{_libdir}/libu2mfn.so
/etc/yum.real.repos.d/qubes-cached.repo
/etc/sudoers.d/qubes
/etc/xdg/autostart/qubes-guid.desktop
/etc/security/limits.d/99-qubes.conf
/etc/xen/xl.conf
/etc/yum/post-actions/qubes_sync_rpmdb_updatevm.action