Merge branch 'core3-devel'

This commit is contained in:
Marek Marczykowski-Górecki 2017-05-20 14:29:51 +02:00
commit ce70887a57
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
37 changed files with 1529 additions and 227 deletions

3
.coveragerc Normal file
View File

@ -0,0 +1,3 @@
[run]
source = qubesagent
omit = qubesagent/test*

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ deb/*
*.pyo *.pyo
*~ *~
*.o *.o
.coverage
*.egg-info

View File

@ -1,6 +1,7 @@
sudo: required sudo: required
dist: trusty dist: trusty
language: generic language: python
python: '3.5'
install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
script: ~/qubes-builder/scripts/travis-build script: ~/qubes-builder/scripts/travis-build
env: env:
@ -8,3 +9,22 @@ env:
- DISTS_VM=fc24 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1 - DISTS_VM=fc24 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
- DISTS_VM=jessie USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1 - DISTS_VM=jessie USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
- DISTS_VM=stretch USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1 - DISTS_VM=stretch USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1
jobs:
include:
- python: '3.5'
install: pip install --quiet -r ci/requirements.txt
env: TESTS_ONLY=1
script:
- ./run-tests
after_success:
- codecov
- stage: deploy
python: '3.5'
env: DIST_DOM0=fc25 TESTS_ONLY=
script: ~/qubes-builder/scripts/travis-deploy
branches:
except:
- /.*_.*/

View File

@ -5,6 +5,7 @@ VERSION := $(shell cat version)
DIST ?= fc18 DIST ?= fc18
KDESERVICEDIR ?= /usr/share/kde4/services KDESERVICEDIR ?= /usr/share/kde4/services
SBINDIR ?= /usr/sbin SBINDIR ?= /usr/sbin
BINDIR ?= /usr/bin
LIBDIR ?= /usr/lib LIBDIR ?= /usr/lib
SYSLIBDIR ?= /lib SYSLIBDIR ?= /lib
@ -132,7 +133,6 @@ install-sysvinit: install-init
install vm-init.d/qubes-core $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-core $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-core-netvm $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-core-netvm $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-firewall $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-firewall $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-netwatcher $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-qrexec-agent $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-qrexec-agent $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-updates-proxy $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-updates-proxy $(DESTDIR)/etc/init.d/
install vm-init.d/qubes-dvm $(DESTDIR)/etc/init.d/ install vm-init.d/qubes-dvm $(DESTDIR)/etc/init.d/
@ -142,10 +142,6 @@ install-sysvinit: install-init
install-rh: install-systemd install-systemd-dropins install-sysvinit install-rh: install-systemd install-systemd-dropins install-sysvinit
install -D -m 0644 misc/qubes-r3.repo $(DESTDIR)/etc/yum.repos.d/qubes-r3.repo install -D -m 0644 misc/qubes-r3.repo $(DESTDIR)/etc/yum.repos.d/qubes-r3.repo
install -d $(DESTDIR)/usr/share/glib-2.0/schemas/
install -m 0644 misc/org.gnome.settings-daemon.plugins.updates.gschema.override $(DESTDIR)/usr/share/glib-2.0/schemas/
install -m 0644 misc/org.gnome.nautilus.gschema.override $(DESTDIR)/usr/share/glib-2.0/schemas/
install -m 0644 misc/org.mate.NotificationDaemon.gschema.override $(DESTDIR)/usr/share/glib-2.0/schemas/
install -d $(DESTDIR)$(LIBDIR)/yum-plugins/ install -d $(DESTDIR)$(LIBDIR)/yum-plugins/
install -m 0644 misc/yum-qubes-hooks.py* $(DESTDIR)$(LIBDIR)/yum-plugins/ install -m 0644 misc/yum-qubes-hooks.py* $(DESTDIR)$(LIBDIR)/yum-plugins/
install -D -m 0644 misc/yum-qubes-hooks.conf $(DESTDIR)/etc/yum/pluginconf.d/yum-qubes-hooks.conf install -D -m 0644 misc/yum-qubes-hooks.conf $(DESTDIR)/etc/yum/pluginconf.d/yum-qubes-hooks.conf
@ -174,6 +170,11 @@ install-common:
$(MAKE) -C autostart-dropins install $(MAKE) -C autostart-dropins install
install -m 0644 -D misc/fstab $(DESTDIR)/etc/fstab install -m 0644 -D misc/fstab $(DESTDIR)/etc/fstab
# force /usr/bin before /bin to have /usr/bin/python instead of /bin/python
PATH="/usr/bin:$(PATH)" python3 setup.py install -O1 --root $(DESTDIR)
mkdir -p $(DESTDIR)$(SBINDIR)
mv $(DESTDIR)/usr/bin/qubes-firewall $(DESTDIR)$(SBINDIR)/qubes-firewall
install -d -m 0750 $(DESTDIR)/etc/sudoers.d/ install -d -m 0750 $(DESTDIR)/etc/sudoers.d/
install -D -m 0440 misc/qubes.sudoers $(DESTDIR)/etc/sudoers.d/qubes install -D -m 0440 misc/qubes.sudoers $(DESTDIR)/etc/sudoers.d/qubes
install -D -m 0440 misc/sudoers.d_qt_x11_no_mitshm $(DESTDIR)/etc/sudoers.d/qt_x11_no_mitshm install -D -m 0440 misc/sudoers.d_qt_x11_no_mitshm $(DESTDIR)/etc/sudoers.d/qt_x11_no_mitshm
@ -181,7 +182,7 @@ install-common:
install -d $(DESTDIR)/var/lib/qubes install -d $(DESTDIR)/var/lib/qubes
install -D misc/xenstore-watch $(DESTDIR)/usr/bin/xenstore-watch-qubes install -D misc/xenstore-watch $(DESTDIR)$(BINDIR)/xenstore-watch-qubes
install -d $(DESTDIR)/etc/udev/rules.d install -d $(DESTDIR)/etc/udev/rules.d
install -m 0644 misc/udev-qubes-misc.rules $(DESTDIR)/etc/udev/rules.d/50-qubes-misc.rules install -m 0644 misc/udev-qubes-misc.rules $(DESTDIR)/etc/udev/rules.d/50-qubes-misc.rules
install -d $(DESTDIR)$(LIBDIR)/qubes/ install -d $(DESTDIR)$(LIBDIR)/qubes/
@ -192,6 +193,12 @@ install-common:
install -D -m 0644 misc/polkit-1-qubes-allow-all.rules $(DESTDIR)/etc/polkit-1/rules.d/00-qubes-allow-all.rules install -D -m 0644 misc/polkit-1-qubes-allow-all.rules $(DESTDIR)/etc/polkit-1/rules.d/00-qubes-allow-all.rules
install -D -m 0644 misc/mime-globs $(DESTDIR)/usr/share/qubes/mime-override/globs install -D -m 0644 misc/mime-globs $(DESTDIR)/usr/share/qubes/mime-override/globs
install misc/qubes-download-dom0-updates.sh $(DESTDIR)$(LIBDIR)/qubes/ install misc/qubes-download-dom0-updates.sh $(DESTDIR)$(LIBDIR)/qubes/
install -d $(DESTDIR)/usr/share/glib-2.0/schemas/
install -m 0644 \
misc/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override \
misc/20_org.gnome.nautilus.qubes.gschema.override \
misc/20_org.mate.NotificationDaemon.qubes.gschema.override \
$(DESTDIR)/usr/share/glib-2.0/schemas/
install -g user -m 2775 -d $(DESTDIR)/var/lib/qubes/dom0-updates install -g user -m 2775 -d $(DESTDIR)/var/lib/qubes/dom0-updates
install -D -m 0644 misc/qubes-master-key.asc $(DESTDIR)/usr/share/qubes/qubes-master-key.asc install -D -m 0644 misc/qubes-master-key.asc $(DESTDIR)/usr/share/qubes/qubes-master-key.asc
@ -213,6 +220,7 @@ install-common:
install -d $(DESTDIR)/usr/lib/NetworkManager/conf.d install -d $(DESTDIR)/usr/lib/NetworkManager/conf.d
install -m 0644 network/nm-30-qubes.conf $(DESTDIR)/usr/lib/NetworkManager/conf.d/30-qubes.conf install -m 0644 network/nm-30-qubes.conf $(DESTDIR)/usr/lib/NetworkManager/conf.d/30-qubes.conf
install -D network/vif-route-qubes $(DESTDIR)/etc/xen/scripts/vif-route-qubes install -D network/vif-route-qubes $(DESTDIR)/etc/xen/scripts/vif-route-qubes
install -D network/vif-qubes-nat.sh $(DESTDIR)/etc/xen/scripts/vif-qubes-nat.sh
install -m 0644 -D network/tinyproxy-updates.conf $(DESTDIR)/etc/tinyproxy/tinyproxy-updates.conf install -m 0644 -D network/tinyproxy-updates.conf $(DESTDIR)/etc/tinyproxy/tinyproxy-updates.conf
install -m 0644 -D network/updates-blacklist $(DESTDIR)/etc/tinyproxy/updates-blacklist install -m 0644 -D network/updates-blacklist $(DESTDIR)/etc/tinyproxy/updates-blacklist
install -m 0755 -D network/iptables-updates-proxy $(DESTDIR)$(LIBDIR)/qubes/iptables-updates-proxy install -m 0755 -D network/iptables-updates-proxy $(DESTDIR)$(LIBDIR)/qubes/iptables-updates-proxy
@ -223,14 +231,9 @@ install-common:
install -m 0400 -D network/ip6tables $(DESTDIR)/etc/qubes/ip6tables.rules install -m 0400 -D network/ip6tables $(DESTDIR)/etc/qubes/ip6tables.rules
install -m 0755 network/update-proxy-configs $(DESTDIR)$(LIBDIR)/qubes/ install -m 0755 network/update-proxy-configs $(DESTDIR)$(LIBDIR)/qubes/
install -d $(DESTDIR)$(BINDIR)
install -d $(DESTDIR)/$(SBINDIR) install -m 0755 misc/qubes-session-autostart $(DESTDIR)$(BINDIR)/qubes-session-autostart
install network/qubes-firewall $(DESTDIR)/$(SBINDIR)/ install -m 0755 misc/qvm-features-request $(DESTDIR)$(BINDIR)/qvm-features-request
install network/qubes-netwatcher $(DESTDIR)/$(SBINDIR)/
install -d $(DESTDIR)/usr/bin
install -m 0755 misc/qubes-session-autostart $(DESTDIR)/usr/bin/qubes-session-autostart
install qubes-rpc/{qvm-open-in-dvm,qvm-open-in-vm,qvm-copy-to-vm,qvm-run,qvm-mru-entry} $(DESTDIR)/usr/bin install qubes-rpc/{qvm-open-in-dvm,qvm-open-in-vm,qvm-copy-to-vm,qvm-run,qvm-mru-entry} $(DESTDIR)/usr/bin
ln -s qvm-copy-to-vm $(DESTDIR)/usr/bin/qvm-move-to-vm ln -s qvm-copy-to-vm $(DESTDIR)/usr/bin/qvm-move-to-vm
install qubes-rpc/qvm-copy-to-vm.kde $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/qvm-copy-to-vm.kde $(DESTDIR)$(LIBDIR)/qubes
@ -239,7 +242,7 @@ install-common:
install qubes-rpc/qvm-move-to-vm.gnome $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/qvm-move-to-vm.gnome $(DESTDIR)$(LIBDIR)/qubes
install qubes-rpc/xdg-icon $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/xdg-icon $(DESTDIR)$(LIBDIR)/qubes
install qubes-rpc/{vm-file-editor,qfile-agent,qopen-in-vm} $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/{vm-file-editor,qfile-agent,qopen-in-vm} $(DESTDIR)$(LIBDIR)/qubes
install qubes-rpc/qubes-open $(DESTDIR)/usr/bin install qubes-rpc/qubes-open $(DESTDIR)$(BINDIR)
install qubes-rpc/tar2qfile $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/tar2qfile $(DESTDIR)$(LIBDIR)/qubes
# Install qfile-unpacker as SUID - because it will fail to receive files from other vm # Install qfile-unpacker as SUID - because it will fail to receive files from other vm
install -m 4755 qubes-rpc/qfile-unpacker $(DESTDIR)$(LIBDIR)/qubes install -m 4755 qubes-rpc/qfile-unpacker $(DESTDIR)$(LIBDIR)/qubes
@ -262,6 +265,8 @@ install-common:
install -m 0644 qubes-rpc/qubes.GetImageRGBA $(DESTDIR)/etc/qubes-rpc install -m 0644 qubes-rpc/qubes.GetImageRGBA $(DESTDIR)/etc/qubes-rpc
install -m 0644 qubes-rpc/qubes.SetDateTime $(DESTDIR)/etc/qubes-rpc install -m 0644 qubes-rpc/qubes.SetDateTime $(DESTDIR)/etc/qubes-rpc
install -m 0755 qubes-rpc/qubes.InstallUpdatesGUI $(DESTDIR)/etc/qubes-rpc install -m 0755 qubes-rpc/qubes.InstallUpdatesGUI $(DESTDIR)/etc/qubes-rpc
install -m 0755 qubes-rpc/qubes.ResizeDisk $(DESTDIR)/etc/qubes-rpc
install -m 0755 qubes-rpc/qubes.StartApp $(DESTDIR)/etc/qubes-rpc
install -d $(DESTDIR)/etc/qubes/suspend-pre.d install -d $(DESTDIR)/etc/qubes/suspend-pre.d
install -m 0644 qubes-rpc/suspend-pre.README $(DESTDIR)/etc/qubes/suspend-pre.d/README install -m 0644 qubes-rpc/suspend-pre.README $(DESTDIR)/etc/qubes/suspend-pre.d/README
@ -271,14 +276,14 @@ install-common:
install -d $(DESTDIR)/usr/share/nautilus-python/extensions install -d $(DESTDIR)/usr/share/nautilus-python/extensions
install -m 0644 qubes-rpc/*_nautilus.py $(DESTDIR)/usr/share/nautilus-python/extensions install -m 0644 qubes-rpc/*_nautilus.py $(DESTDIR)/usr/share/nautilus-python/extensions
install -D -m 0755 misc/qubes-desktop-run $(DESTDIR)/usr/bin/qubes-desktop-run install -D -m 0755 misc/qubes-desktop-run $(DESTDIR)$(BINDIR)/qubes-desktop-run
mkdir -p $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ mkdir -p $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/
ifeq ($(shell lsb_release -is), Debian) ifeq ($(shell lsb_release -is), Debian)
install -m 0644 misc/xdg.py $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ install -m 0644 misc/qubesxdg.py $(DESTDIR)/$(PYTHON2_SITELIB)/
else else
install -m 0644 misc/py2/xdg.py* $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ install -m 0644 misc/py2/qubesxdg.py* $(DESTDIR)/$(PYTHON2_SITELIB)/
endif endif
ifneq (,$(filter xenial stretch, $(shell lsb_release -cs))) ifneq (,$(filter xenial stretch, $(shell lsb_release -cs)))
@ -311,8 +316,6 @@ install-deb: install-common install-systemd install-systemd-dropins
install -m 0644 misc/pam.d_su.qubes $(DESTDIR)/etc/pam.d/su.qubes install -m 0644 misc/pam.d_su.qubes $(DESTDIR)/etc/pam.d/su.qubes
install -d $(DESTDIR)/etc/needrestart/conf.d install -d $(DESTDIR)/etc/needrestart/conf.d
install -D -m 0644 misc/50_qubes.conf $(DESTDIR)/etc/needrestart/conf.d/50_qubes.conf install -D -m 0644 misc/50_qubes.conf $(DESTDIR)/etc/needrestart/conf.d/50_qubes.conf
install -d $(DESTDIR)/usr/share/glib-2.0/schemas/
install -m 0644 misc/org.gnome.nautilus.gschema.override $(DESTDIR)/usr/share/glib-2.0/schemas/
install-vm: install-rh install-common install-vm: install-rh install-common

View File

@ -56,7 +56,6 @@ sed 's:#!/usr/bin/env python:#!/usr/bin/env python2:' -i qubes-rpc/*
# Fix for archlinux sbindir # Fix for archlinux sbindir
sed 's:/usr/sbin/ntpdate:/usr/bin/ntpdate:g' -i qubes-rpc/sync-ntp-clock sed 's:/usr/sbin/ntpdate:/usr/bin/ntpdate:g' -i qubes-rpc/sync-ntp-clock
sed 's:/usr/sbin/qubes-netwatcher:/usr/bin/qubes-netwatcher:g' -i vm-systemd/qubes-netwatcher.service
sed 's:/usr/sbin/qubes-firewall:/usr/bin/qubes-firewall:g' -i vm-systemd/qubes-firewall.service sed 's:/usr/sbin/qubes-firewall:/usr/bin/qubes-firewall:g' -i vm-systemd/qubes-firewall.service
for dir in qubes-rpc qrexec misc; do for dir in qubes-rpc qrexec misc; do

View File

@ -475,7 +475,7 @@ post_remove() {
rm -rf /var/lib/qubes/xdg rm -rf /var/lib/qubes/xdg
for srv in qubes-dvm qubes-sysinit qubes-misc-post qubes-mount-dirs qubes-netwatcher qubes-network qubes-qrexec-agent; do for srv in qubes-dvm qubes-sysinit qubes-misc-post qubes-mount-dirs qubes-network qubes-qrexec-agent; do
systemctl disable $srv.service systemctl disable $srv.service
done done

6
ci/requirements.txt Normal file
View File

@ -0,0 +1,6 @@
# WARNING: those requirements are used only for travis-ci.org
# they SHOULD NOT be used under normal conditions; use system package manager
docutils
pylint
codecov
python-daemon

2
debian/control vendored
View File

@ -7,6 +7,7 @@ Build-Depends:
libqubes-rpc-filecopy-dev (>= 3.1.3), libqubes-rpc-filecopy-dev (>= 3.1.3),
libvchan-xen-dev, libvchan-xen-dev,
python, python,
python3-setuptools,
debhelper, debhelper,
quilt, quilt,
libxen-dev, libxen-dev,
@ -40,6 +41,7 @@ Depends:
procps, procps,
util-linux, util-linux,
python2.7, python2.7,
python-daemon,
python-gi, python-gi,
python-xdg, python-xdg,
python-dbus, python-dbus,

View File

@ -43,7 +43,7 @@ if [ "${1}" = "remove" ] ; then
rm /lib/firmware/updates rm /lib/firmware/updates
fi fi
for srv in qubes-dvm qubes-sysinit qubes-misc-post qubes-netwatcher qubes-network qubes-qrexec-agent; do for srv in qubes-dvm qubes-sysinit qubes-misc-post qubes-network qubes-qrexec-agent; do
systemctl disable ${srv}.service systemctl disable ${srv}.service
done done
fi fi

View File

@ -23,7 +23,7 @@ python2:
python3: python3:
rm -rf py3 rm -rf py3
mkdir -p py3 mkdir -p py3
cp dnf-qubes-hooks.py xdg.py py3/ cp dnf-qubes-hooks.py qubesxdg.py py3/
python3 -m compileall py3 python3 -m compileall py3
python3 -O -m compileall py3 python3 -O -m compileall py3

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
from qubes.xdg import launch from qubesxdg import launch
import sys import sys
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,7 +2,6 @@
DOM0_UPDATES_DIR=/var/lib/qubes/dom0-updates DOM0_UPDATES_DIR=/var/lib/qubes/dom0-updates
DOIT=0
GUI=1 GUI=1
CLEAN=0 CLEAN=0
CHECK_ONLY=0 CHECK_ONLY=0
@ -17,7 +16,7 @@ export LC_ALL=C
while [ -n "$1" ]; do while [ -n "$1" ]; do
case "$1" in case "$1" in
--doit) --doit)
DOIT=1 # ignore
;; ;;
--nogui) --nogui)
GUI=0 GUI=0
@ -80,48 +79,30 @@ if [ "$CLEAN" = "1" ]; then
rm -rf $DOM0_UPDATES_DIR/var/cache/yum/* rm -rf $DOM0_UPDATES_DIR/var/cache/yum/*
fi fi
if [ "x$PKGLIST" = "x" ]; then # just check for updates, but don't download any package
if [ "x$PKGLIST" = "x" -a "$CHECK_ONLY" = "1" ]; then
echo "Checking for dom0 updates..." >&2 echo "Checking for dom0 updates..." >&2
UPDATES_FULL=`$YUM $OPTS check-update` UPDATES_FULL=`$YUM $OPTS check-update`
check_update_retcode=$? check_update_retcode=$?
UPDATES_FULL=`echo "$UPDATES_FULL" | grep -v "^Loaded plugins:\|^Last metadata\|^$"`
if [ $check_update_retcode -eq 1 ]; then if [ $check_update_retcode -eq 1 ]; then
# Exit here if yum have reported an error. Exit code 100 isn't an # Exit here if yum have reported an error. Exit code 100 isn't an
# error, it's "updates available" info, so check specifically for exit code 1 # error, it's "updates available" info, so check specifically for exit code 1
exit 1 exit 1
fi fi
UPDATES=`echo "$UPDATES_FULL" | grep -v "^Obsoleting\|Could not" | cut -f 1 -d ' '` if [ $check_update_retcode -eq 100 ]; then
if [ -z "$UPDATES" -a $check_update_retcode -eq 100 ]; then echo "Available updates: "
# save not empty string for below condition (-z "$UPDATES"), but blank echo "$UPDATES_FULL"
# to not confuse the user wwith magic strings in messages exit 100
UPDATES=" "
elif [ $check_update_retcode -eq 0 ]; then
# exit code 0 means no updates available - regardless of stdout messages
UPDATES=""
fi
else else
PKGS_FROM_CMDLINE=1
fi
if [ -z "$PKGLIST" -a -z "$UPDATES" ]; then
echo "No new updates available" echo "No new updates available"
if [ "$GUI" = 1 ]; then if [ "$GUI" = 1 ]; then
zenity --info --text="No new updates available" zenity --info --text="No new updates available"
fi fi
exit 0 exit 0
fi fi
if [ "$CHECK_ONLY" = "1" ]; then
echo "Available updates: "
echo "$UPDATES_FULL"
exit 100
fi
if [ "$DOIT" != "1" -a "$PKGS_FROM_CMDLINE" != "1" ]; then
zenity --question --title="Qubes Dom0 updates" \
--text="There are updates for dom0 available, do you want to download them now?" || exit 0
fi fi
# now, we will download something
YUM_COMMAND="fakeroot $YUM $YUM_ACTION -y --downloadonly" YUM_COMMAND="fakeroot $YUM $YUM_ACTION -y --downloadonly"
# check for --downloadonly option - if not supported (Debian), fallback to # check for --downloadonly option - if not supported (Debian), fallback to
# yumdownloader # yumdownloader
@ -135,6 +116,15 @@ if ! $YUM --help | grep -q downloadonly; then
exit 1 exit 1
fi fi
if [ "$YUM_ACTION" = "upgrade" ]; then if [ "$YUM_ACTION" = "upgrade" ]; then
UPDATES_FULL=`$YUM $OPTS check-update $PKGLIST`
check_update_retcode=$?
UPDATES_FULL=`echo "$UPDATES_FULL" | grep -v "^Loaded plugins:\|^Last metadata\|^$"`
UPDATES=`echo "$UPDATES_FULL" | grep -v "^Obsoleting\|Could not" | cut -f 1 -d ' '`
if [ $check_update_retcode -eq 0 ]; then
# exit code 0 means no updates available - regardless of stdout messages
echo "No new updates available"
exit 0
fi
PKGLIST=$UPDATES PKGLIST=$UPDATES
fi fi
YUM_COMMAND="yumdownloader --destdir=$DOM0_UPDATES_DIR/packages --resolve" YUM_COMMAND="yumdownloader --destdir=$DOM0_UPDATES_DIR/packages --resolve"

View File

@ -25,7 +25,7 @@ import subprocess
import sys import sys
from xdg.DesktopEntry import DesktopEntry from xdg.DesktopEntry import DesktopEntry
from qubes.xdg import launch from qubesxdg import launch
import xdg.BaseDirectory import xdg.BaseDirectory
import os import os

81
misc/qvm-features-request Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env python2
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2010-2016 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2016 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.
#
import argparse
import os
import subprocess
import sys
import qubesdb
class FeatureRequestAction(argparse.Action):
'''Action for argument parser that stores a property.'''
# pylint: disable=redefined-builtin,too-few-public-methods
def __init__(self,
option_strings,
dest='features',
metavar='NAME=VALUE',
required=False,
help='request a feature with the value'):
super(FeatureRequestAction, self).__init__(option_strings, dest=dest,
metavar=metavar, nargs='*', required=required, default={},
help=help)
def __call__(self, parser, namespace, values, option_string=None):
for request in values:
try:
feature, value = request.split('=', 1)
except ValueError:
parser.error(
'invalid feature request token: {!r}'.format(request))
getattr(namespace, self.dest)[feature] = value
parser = argparse.ArgumentParser(
description='submit a feature request to the dom0')
parser.add_argument('--commit',
action='store_true', default=False,
help='actually send the request (without it, only make entries in qubesdb)')
parser.add_argument('features',
action=FeatureRequestAction)
def main(args=None):
args = parser.parse_args(args)
qdb = qubesdb.QubesDB()
for feature, value in args.features.items():
qdb.write('/features-request/' + feature, value)
if args.commit:
devnull = os.open(os.devnull, os.O_RDWR)
subprocess.check_call(
['qrexec-client-vm', 'dom0', 'qubes.FeaturesRequest'],
stdin=devnull, stdout=devnull)
if __name__ == '__main__':
sys.exit(main())

View File

@ -3,6 +3,8 @@
:INPUT DROP [1:72] :INPUT DROP [1:72]
:FORWARD DROP [0:0] :FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0] :OUTPUT ACCEPT [0:0]
:QBS-FORWARD - [0:0]
-A INPUT -i lo -j ACCEPT -A INPUT -i lo -j ACCEPT
-A FORWARD -j QBS-FORWARD
COMMIT COMMIT
# Completed on Tue Sep 25 16:00:20 2012 # Completed on Tue Sep 25 16:00:20 2012

View File

@ -17,6 +17,7 @@ COMMIT
:INPUT ACCEPT [168:11399] :INPUT ACCEPT [168:11399]
:FORWARD ACCEPT [0:0] :FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [128:12536] :OUTPUT ACCEPT [128:12536]
:QBS-FORWARD - [0:0]
-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP -A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i vif+ -p icmp -j ACCEPT -A INPUT -i vif+ -p icmp -j ACCEPT
@ -24,6 +25,7 @@ COMMIT
-A INPUT -i vif+ -j REJECT --reject-with icmp-host-prohibited -A INPUT -i vif+ -j REJECT --reject-with icmp-host-prohibited
-A INPUT -j DROP -A INPUT -j DROP
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j QBS-FORWARD
-A FORWARD -i vif+ -o vif+ -j DROP -A FORWARD -i vif+ -o vif+ -j DROP
-A FORWARD -i vif+ -j ACCEPT -A FORWARD -i vif+ -j ACCEPT
-A FORWARD -j DROP -A FORWARD -j DROP

View File

@ -1,62 +0,0 @@
#!/bin/sh
set -e
PIDFILE=/var/run/qubes/qubes-firewall.pid
XENSTORE_IPTABLES=/qubes-iptables
XENSTORE_IPTABLES_HEADER=/qubes-iptables-header
XENSTORE_ERROR=/qubes-iptables-error
OLD_RULES=""
# PIDfile handling
[ -e "$PIDFILE" ] && kill -s 0 $(cat "$PIDFILE") 2>/dev/null && exit 0
echo $$ >$PIDFILE
trap 'exit 0' TERM
FIRST_TIME=yes
while true; do
echo "1" > /proc/sys/net/ipv4/ip_forward
if [ "$FIRST_TIME" ]; then
FIRST_TIME=
TRIGGER=reload
else
# Wait for changes in qubesdb file
qubesdb-watch $XENSTORE_IPTABLES
TRIGGER=$(qubesdb-read $XENSTORE_IPTABLES)
fi
if ! [ "$TRIGGER" = "reload" ]; then continue ; fi
# Disable forwarding to prevent potential "leaks" that might
# be bypassing the firewall or some proxy service (e.g. tor)
# during the time when the rules are being (re)applied
echo "0" > /proc/sys/net/ipv4/ip_forward
RULES=$(qubesdb-read $XENSTORE_IPTABLES_HEADER)
IPTABLES_SAVE=$(iptables-save | sed '/^\*filter/,/^COMMIT/d')
OUT=$(printf '%s\n%s\n' "$RULES" "$IPTABLES_SAVE" | sed 's/\\n\|\\x0a/\n/g' | iptables-restore 2>&1 || true)
for i in $(qubesdb-list -f /qubes-iptables-domainrules) ; do
RULES=$(qubesdb-read "$i")
ERRS=$(printf '%s\n' "$RULES" | sed 's/\\n\|\\x0a/\n/g' | /sbin/iptables-restore -n 2>&1 || true)
if [ -n "$ERRS" ]; then
echo "Failed applying rules for $i: $ERRS" >&2
OUT="$OUT$ERRS"
fi
done
qubesdb-write $XENSTORE_ERROR "$OUT"
if [ -n "$OUT" ]; then
DISPLAY=:0 /usr/bin/notify-send -t 3000 "Firewall loading error ($(hostname))" "$OUT" || :
fi
if [ `systemctl is-active qubes-updates-proxy` = "active" ]; then
iptables -I INPUT -i vif+ -p tcp --dport 8082 -j ACCEPT
fi
# Check if user didn't define some custom rules to be applied as well...
[ -x /rw/config/qubes-firewall-user-script ] && /rw/config/qubes-firewall-user-script
# XXX: Backward compatibility
[ -x /rw/config/qubes_firewall_user_script ] && /rw/config/qubes_firewall_user_script
done

View File

@ -1,31 +0,0 @@
#!/bin/sh
set -e
PIDFILE=/var/run/qubes/qubes-netwatcher.pid
CURR_NETCFG=""
# PIDfile handling
[ -e "$PIDFILE" ] && kill -s 0 $(cat "$PIDFILE") 2>/dev/null && exit 0
echo $$ >$PIDFILE
trap 'exit 0' TERM
while true; do
NET_DOMID=$(xenstore-read qubes-netvm-domid || :)
if [ -n "$NET_DOMID" ] && [ $NET_DOMID -gt 0 ]; then
UNTRUSTED_NETCFG=$(xenstore-read /local/domain/$NET_DOMID/qubes-netvm-external-ip || :)
# UNTRUSTED_NETCFG is not parsed in any way
# thus, no sanitization ready
# but be careful when passing it to other shell scripts
if [ "$UNTRUSTED_NETCFG" != "$CURR_NETCFG" ]; then
/sbin/service qubes-firewall stop
/sbin/service qubes-firewall start
CURR_NETCFG="$UNTRUSTED_NETCFG"
xenstore-write qubes-netvm-external-ip "$CURR_NETCFG"
fi
xenstore-watch -n 3 /local/domain/$NET_DOMID/qubes-netvm-external-ip qubes-netvm-domid
else
xenstore-watch -n 2 qubes-netvm-domid
fi
done

102
network/vif-qubes-nat.sh Executable file
View File

@ -0,0 +1,102 @@
#!/bin/bash
#set -x
undetectable_netvm_ips=
netns="${vif}-nat"
netvm_if="${vif}"
netns_netvm_if="${vif}-p"
netns_appvm_if="${vif}"
#
# .----------------------------------.
# | NetVM/ProxyVM |
# .------------.|.------------------. |
# | AppVM ||| $netns namespace | |
# | ||| | |
# | eth0<--------->$netns_appvm_if | |
# |$appvm_ip ||| $appvm_gw_ip | |
# |$appvm_gw_ip||| ^ | |
# '------------'|| |NAT | |
# || v | |
# || $netns_netvm_if<--->$netvm_if |
# || $netvm_ip | $netvm_gw_ip|
# |'------------------' |
# '----------------------------------'
#
function run
{
#echo "$@" >> /var/log/qubes-nat.log
"$@"
}
function netns
{
run ip netns exec "$netns" "$@"
}
run ip addr flush dev "$netns_appvm_if"
run ip netns delete "$netns" || :
if test "$command" == online; then
run ip netns add "$netns"
run ip link set "$netns_appvm_if" netns "$netns"
# keep the same MAC as the real vif interface, so NetworkManager will still
# ignore it
run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" address fe:ff:ff:ff:ff:ff
run ip link set "$netns_netvm_if" netns "$netns"
netns ip6tables -t raw -I PREROUTING -j DROP
netns ip6tables -P INPUT DROP
netns ip6tables -P FORWARD DROP
netns ip6tables -P OUTPUT DROP
netns sh -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'
netns iptables -t raw -I PREROUTING -i "$netns_appvm_if" ! -s "$appvm_ip" -j DROP
if test -n "$undetectable_netvm_ips"; then
# prevent an AppVM connecting to its own ProxyVM IP because that makes the internal IPs detectable even with no firewall rules
netns iptables -t raw -I PREROUTING -i "$netns_appvm_if" -d "$netvm_ip" -j DROP
# same for the gateway/DNS IPs
netns iptables -t raw -I PREROUTING -i "$netns_appvm_if" -d "$netvm_gw_ip" -j DROP
netns iptables -t raw -I PREROUTING -i "$netns_appvm_if" -d "$netvm_dns1_ip" -j DROP
netns iptables -t raw -I PREROUTING -i "$netns_appvm_if" -d "$netvm_dns2_ip" -j DROP
fi
netns iptables -t nat -I PREROUTING -i "$netns_netvm_if" -j DNAT --to-destination "$appvm_ip"
netns iptables -t nat -I POSTROUTING -o "$netns_netvm_if" -j SNAT --to-source "$netvm_ip"
netns iptables -t nat -I PREROUTING -i "$netns_appvm_if" -d "$appvm_gw_ip" -j DNAT --to-destination "$netvm_gw_ip"
netns iptables -t nat -I POSTROUTING -o "$netns_appvm_if" -s "$netvm_gw_ip" -j SNAT --to-source "$appvm_gw_ip"
if test -n "$appvm_dns1_ip"; then
netns iptables -t nat -I PREROUTING -i "$netns_appvm_if" -d "$appvm_dns1_ip" -j DNAT --to-destination "$netvm_dns1_ip"
netns iptables -t nat -I POSTROUTING -o "$netns_appvm_if" -s "$netvm_dns1_ip" -j SNAT --to-source "$appvm_dns1_ip"
fi
if test -n "$appvm_dns2_ip"; then
netns iptables -t nat -I PREROUTING -i "$netns_appvm_if" -d "$appvm_dns2_ip" -j DNAT --to-destination "$netvm_dns2_ip"
netns iptables -t nat -I POSTROUTING -o "$netns_appvm_if" -s "$netvm_dns2_ip" -j SNAT --to-source "$appvm_dns2_ip"
fi
netns ip addr add "$netvm_ip" dev "$netns_netvm_if"
netns ip addr add "$appvm_gw_ip" dev "$netns_appvm_if"
netns ip link set "$netns_netvm_if" up
netns ip link set "$netns_appvm_if" up
netns ip route add "$appvm_ip" dev "$netns_appvm_if" src "$appvm_gw_ip"
netns ip route add "$netvm_gw_ip" dev "$netns_netvm_if" src "$netvm_ip"
netns ip route add default via "$netvm_gw_ip" dev "$netns_netvm_if" src "$netvm_ip"
#run ip addr add "$netvm_gw_ip" dev "$netvm_if"
#run ip link set "$netvm_if" up
#run ip route add "$netvm_ip" dev "$netvm_if" src "$netvm_gw_ip"
fi

View File

@ -26,6 +26,31 @@ dir=$(dirname "$0")
#main_ip=$(dom0_ip) #main_ip=$(dom0_ip)
lockfile=/var/run/xen-hotplug/vif-lock lockfile=/var/run/xen-hotplug/vif-lock
if [ "${ip}" ]; then
# IPs as seen by this VM
netvm_ip="$ip"
netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway`
netvm_dns1_ip=`qubesdb-read /qubes-netvm-primary-dns`
netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns`
back_ip="$netvm_gw_ip"
# IPs as seen by the VM - if other than $netvm_ip
appvm_gw_ip="`qubesdb-read /mapped-ip/$ip/visible-gateway 2>/dev/null || :`"
appvm_ip="`qubesdb-read /mapped-ip/$ip/visible-ip 2>/dev/null || :`"
fi
# Apply NAT if IP visible from the VM is different than the "real" one
# See vif-qubes-nat.sh for details
if [ -n "$appvm_ip" -a -n "$appvm_gw_ip" -a "$appvm_ip" != "$netvm_ip" ]; then
if test "$command" == online; then
echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp
fi
. "$dir/vif-qubes-nat.sh"
fi
case "$command" in case "$command" in
online) online)
ifconfig ${vif} up ifconfig ${vif} up
@ -56,7 +81,6 @@ if [ "${ip}" ] ; then
done done
echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \
${cmdprefix} flock $lockfile iptables-restore --noflush ${cmdprefix} flock $lockfile iptables-restore --noflush
back_ip=`qubesdb-read /qubes-netvm-gateway`
${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif}
fi fi

32
qubes-rpc/qubes.ResizeDisk Executable file
View File

@ -0,0 +1,32 @@
#!/bin/sh
read disk_name
set -e
case $disk_name in
private)
# force some read to refresh device size
head /dev/xvdb > /dev/null
resize2fs /dev/xvdb
;;
root)
# force some read to refresh device size
head /dev/xvda > /dev/null
new_size=$(cat /sys/block/xvda/size)
ro=$(/sys/block/xvda/ro)
if [ $ro -eq 1 ]; then
new_table="0 $new_size snapshot /dev/xvda /dev/xvdc2 N 16"
else
new_table="0 $new_size linear /dev/xvda 0"
fi
dmsetup load dmroot --table "$new_table"
dmsetup resume dmroot
resize2fs /dev/mapper/dmroot
;;
*)
echo "Automatic resize of '$disk_name' not supported" >&2
exit 1
;;
esac

24
qubes-rpc/qubes.StartApp Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "This service require an argument" >&2
exit 1
fi
# make sure it have .desktop suffix, and only one of it
app_basename="${1%.desktop}.desktop"
# Based on XDG Base Directory Specification, Version 0.7
[ -n "$XDG_DATA_HOME" ] || XDG_DATA_HOME="$HOME/.local/share"
[ -n "$XDG_DATA_DIRS" ] || XDG_DATA_DIRS="/usr/local/share:/usr/share"
for dir in $(echo "$XDG_DATA_HOME:$XDG_DATA_DIRS" | tr : ' '); do
if ! [ -d "$dir/applications" ]; then
continue
fi
if [ -f "$dir/applications/$app_basename" ]; then
exec qubes-desktop-run "$dir/applications/$app_basename"
fi
done
echo "applications/$app_basename not found in $XDG_DATA_HOME:$XDG_DATA_DIRS" >&2
exit 1

0
qubesagent/__init__.py Normal file
View File

576
qubesagent/firewall.py Executable file
View File

@ -0,0 +1,576 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2016
# Marek Marczykowski-Górecki <marmarek@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.
#
import logging
import os
import socket
import subprocess
from distutils import spawn
import daemon
import qubesdb
import sys
import signal
class RuleParseError(Exception):
pass
class RuleApplyError(Exception):
pass
class FirewallWorker(object):
def __init__(self):
self.terminate_requested = False
self.qdb = qubesdb.QubesDB()
self.log = logging.getLogger('qubes.firewall')
self.log.addHandler(logging.StreamHandler(sys.stderr))
def init(self):
'''Create appropriate chains/tables'''
raise NotImplementedError
def cleanup(self):
'''Remove tables/chains - reverse work done by init'''
raise NotImplementedError
def apply_rules(self, source_addr, rules):
'''Apply rules in given source address'''
raise NotImplementedError
def read_rules(self, target):
'''Read rules from QubesDB and return them as a list of dicts'''
entries = self.qdb.multiread('/qubes-firewall/{}/'.format(target))
assert isinstance(entries, dict)
# drop full path
entries = dict(((k.split('/')[3], v) for k, v in entries.items()))
if 'policy' not in entries:
raise RuleParseError('No \'policy\' defined')
policy = entries.pop('policy')
rules = []
for ruleno, rule in sorted(entries.items()):
if len(ruleno) != 4 or not ruleno.isdigit():
raise RuleParseError(
'Unexpected non-rule found: {}={}'.format(ruleno, rule))
rule_dict = dict(elem.split('=') for elem in rule.split(' '))
if 'action' not in rule_dict:
raise RuleParseError('Rule \'{}\' lack action'.format(rule))
rules.append(rule_dict)
rules.append({'action': policy})
return rules
def list_targets(self):
return set(t.split('/')[2] for t in self.qdb.list('/qubes-firewall/'))
@staticmethod
def is_ip6(addr):
return addr.count(':') > 0
def log_error(self, msg):
self.log.error(msg)
subprocess.call(
['notify-send', '-t', '3000', msg],
env=os.environ.copy().update({'DISPLAY': ':0'})
)
def handle_addr(self, addr):
try:
rules = self.read_rules(addr)
self.apply_rules(addr, rules)
except RuleParseError as e:
self.log_error(
'Failed to parse rules for {} ({}), blocking traffic'.format(
addr, str(e)
))
self.apply_rules(addr, [{'action': 'drop'}])
except RuleApplyError as e:
self.log_error(
'Failed to apply rules for {} ({}), blocking traffic'.format(
addr, str(e))
)
# retry with fallback rules
try:
self.apply_rules(addr, [{'action': 'drop'}])
except RuleApplyError:
self.log_error(
'Failed to block traffic for {}'.format(addr))
@staticmethod
def dns_addresses(family=None):
with open('/etc/resolv.conf') as resolv:
for line in resolv.readlines():
line = line.strip()
if line.startswith('nameserver'):
if line.count('.') == 3 and (family or 4) == 4:
yield line.split(' ')[1]
elif line.count(':') and (family or 6) == 6:
yield line.split(' ')[1]
def main(self):
self.terminate_requested = False
self.init()
# initial load
for source_addr in self.list_targets():
self.handle_addr(source_addr)
self.qdb.watch('/qubes-firewall/')
try:
for watch_path in iter(self.qdb.read_watch, None):
# ignore writing rules itself - wait for final write at
# source_addr level empty write (/qubes-firewall/SOURCE_ADDR)
if watch_path.count('/') > 2:
continue
source_addr = watch_path.split('/')[2]
self.handle_addr(source_addr)
except OSError: # EINTR
# signal received, don't continue the loop
pass
self.cleanup()
def terminate(self):
self.terminate_requested = True
class IptablesWorker(FirewallWorker):
supported_rule_opts = ['action', 'proto', 'dst4', 'dst6', 'dsthost',
'dstports', 'specialtarget', 'icmptype']
def __init__(self):
super(IptablesWorker, self).__init__()
self.chains = {
4: set(),
6: set(),
}
@staticmethod
def chain_for_addr(addr):
'''Generate iptables chain name for given source address address'''
return 'qbs-' + addr.replace('.', '-').replace(':', '-')
def run_ipt(self, family, args, **kwargs):
# pylint: disable=no-self-use
if family == 6:
subprocess.check_call(['ip6tables'] + args, **kwargs)
else:
subprocess.check_call(['iptables'] + args, **kwargs)
def run_ipt_restore(self, family, args):
# pylint: disable=no-self-use
if family == 6:
return subprocess.Popen(['ip6tables-restore'] + args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
else:
return subprocess.Popen(['iptables-restore'] + args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
def create_chain(self, addr, chain, family):
'''
Create iptables chain and hook traffic coming from `addr` to it.
:param addr: source IP from which traffic should be handled by the
chain
:param chain: name of the chain to create
:param family: address family (4 or 6)
:return: None
'''
self.run_ipt(family, ['-N', chain])
self.run_ipt(family,
['-A', 'QBS-FORWARD', '-s', addr, '-j', chain])
self.chains[family].add(chain)
def prepare_rules(self, chain, rules, family):
'''
Helper function to translate rules list into input for iptables-restore
:param chain: name of the chain to put rules into
:param rules: list of rules
:param family: address family (4 or 6)
:return: input for iptables-restore
:rtype: str
'''
iptables = "*filter\n"
fullmask = '/128' if family == 6 else '/32'
dns = list(addr + fullmask for addr in self.dns_addresses(family))
for rule in rules:
unsupported_opts = set(rule.keys()).difference(
set(self.supported_rule_opts))
if unsupported_opts:
raise RuleParseError(
'Unsupported rule option(s): {!s}'.format(unsupported_opts))
if 'dst4' in rule and family == 6:
raise RuleParseError('IPv4 rule found for IPv6 address')
if 'dst6' in rule and family == 4:
raise RuleParseError('dst6 rule found for IPv4 address')
if 'proto' in rule:
protos = [rule['proto']]
else:
protos = None
if 'dst4' in rule:
dsthosts = [rule['dst4']]
elif 'dst6' in rule:
dsthosts = [rule['dst6']]
elif 'dsthost' in rule:
addrinfo = socket.getaddrinfo(rule['dsthost'], None,
(socket.AF_INET6 if family == 6 else socket.AF_INET))
dsthosts = set(item[4][0] + fullmask for item in addrinfo)
else:
dsthosts = None
if 'dstports' in rule:
dstports = rule['dstports'].replace('-', ':')
else:
dstports = None
if rule.get('specialtarget', None) == 'dns':
if dstports not in ('53:53', None):
continue
else:
dstports = '53:53'
if not dns:
continue
if protos is not None:
protos = {'tcp', 'udp'}.intersection(protos)
else:
protos = {'tcp', 'udp'}
if dsthosts is not None:
dsthosts = set(dns).intersection(dsthosts)
else:
dsthosts = dns
if 'icmptype' in rule:
icmptype = rule['icmptype']
else:
icmptype = None
# make them iterable
if protos is None:
protos = [None]
if dsthosts is None:
dsthosts = [None]
# sorting here is only to ease writing tests
for proto in sorted(protos):
for dsthost in sorted(dsthosts):
ipt_rule = '-A {}'.format(chain)
if dsthost is not None:
ipt_rule += ' -d {}'.format(dsthost)
if proto is not None:
ipt_rule += ' -p {}'.format(proto)
if dstports is not None:
ipt_rule += ' --dport {}'.format(dstports)
if icmptype is not None:
ipt_rule += ' --icmp-type {}'.format(icmptype)
ipt_rule += ' -j {}\n'.format(
str(rule['action']).upper())
iptables += ipt_rule
iptables += 'COMMIT\n'
return iptables
def apply_rules_family(self, source, rules, family):
'''
Apply rules for given source address.
Handle only rules for given address family (IPv4 or IPv6).
:param source: source address
:param rules: rules list
:param family: address family, either 4 or 6
:return: None
'''
chain = self.chain_for_addr(source)
if chain not in self.chains[family]:
self.create_chain(source, chain, family)
iptables = self.prepare_rules(chain, rules, family)
try:
self.run_ipt(family, ['-F', chain])
p = self.run_ipt_restore(family, ['-n'])
(output, _) = p.communicate(iptables)
if p.returncode != 0:
raise RuleApplyError(
'iptables-restore failed: {}'.format(output))
except subprocess.CalledProcessError as e:
raise RuleApplyError('\'iptables -F {}\' failed: {}'.format(
chain, e.output))
def apply_rules(self, source, rules):
if self.is_ip6(source):
self.apply_rules_family(source, rules, 6)
else:
self.apply_rules_family(source, rules, 4)
def init(self):
# make sure 'QBS_FORWARD' chain exists - should be created before
# starting qubes-firewall
try:
self.run_ipt(4, ['-nL', 'QBS-FORWARD'])
self.run_ipt(6, ['-nL', 'QBS-FORWARD'])
except subprocess.CalledProcessError:
self.log_error('\'QBS-FORWARD\' chain not found, create it first')
sys.exit(1)
def cleanup(self):
for family in (4, 6):
self.run_ipt(family, ['-F', 'QBS-FORWARD'])
for chain in self.chains[family]:
self.run_ipt(family, ['-F', chain])
self.run_ipt(family, ['-X', chain])
class NftablesWorker(FirewallWorker):
supported_rule_opts = ['action', 'proto', 'dst4', 'dst6', 'dsthost',
'dstports', 'specialtarget', 'icmptype']
def __init__(self):
super(NftablesWorker, self).__init__()
self.chains = {
4: set(),
6: set(),
}
@staticmethod
def chain_for_addr(addr):
'''Generate iptables chain name for given source address address'''
return 'qbs-' + addr.replace('.', '-').replace(':', '-')
def run_nft(self, nft_input):
# pylint: disable=no-self-use
p = subprocess.Popen(['nft', '-f', '/dev/stdin'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, _ = p.communicate(nft_input)
if p.returncode != 0:
raise RuleApplyError('nft failed: {}'.format(stdout))
def create_chain(self, addr, chain, family):
'''
Create iptables chain and hook traffic coming from `addr` to it.
:param addr: source IP from which traffic should be handled by the
chain
:param chain: name of the chain to create
:param family: address family (4 or 6)
:return: None
'''
nft_input = (
'table {family} {table} {{\n'
' chain {chain} {{\n'
' }}\n'
' chain forward {{\n'
' {family} saddr {ip} jump {chain}\n'
' }}\n'
'}}\n'.format(
family=("ip6" if family == 6 else "ip"),
table='qubes-firewall',
chain=chain,
ip=addr,
)
)
self.run_nft(nft_input)
self.chains[family].add(chain)
def prepare_rules(self, chain, rules, family):
'''
Helper function to translate rules list into input for iptables-restore
:param chain: name of the chain to put rules into
:param rules: list of rules
:param family: address family (4 or 6)
:return: input for iptables-restore
:rtype: str
'''
assert family in (4, 6)
nft_rules = []
ip_match = 'ip6' if family == 6 else 'ip'
fullmask = '/128' if family == 6 else '/32'
dns = list(addr + fullmask for addr in self.dns_addresses(family))
for rule in rules:
unsupported_opts = set(rule.keys()).difference(
set(self.supported_rule_opts))
if unsupported_opts:
raise RuleParseError(
'Unsupported rule option(s): {!s}'.format(unsupported_opts))
if 'dst4' in rule and family == 6:
raise RuleParseError('IPv4 rule found for IPv6 address')
if 'dst6' in rule and family == 4:
raise RuleParseError('dst6 rule found for IPv4 address')
nft_rule = ""
if 'proto' in rule:
if family == 4:
nft_rule += ' ip protocol {}'.format(rule['proto'])
elif family == 6:
proto = 'icmpv6' if rule['proto'] == 'icmp' \
else rule['proto']
nft_rule += ' ip6 nexthdr {}'.format(proto)
if 'dst4' in rule:
nft_rule += ' ip daddr {}'.format(rule['dst4'])
elif 'dst6' in rule:
nft_rule += ' ip6 daddr {}'.format(rule['dst6'])
elif 'dsthost' in rule:
addrinfo = socket.getaddrinfo(rule['dsthost'], None,
(socket.AF_INET6 if family == 6 else socket.AF_INET))
nft_rule += ' {} daddr {{ {} }}'.format(ip_match,
', '.join(set(item[4][0] + fullmask for item in addrinfo)))
if 'dstports' in rule:
dstports = rule['dstports']
if len(set(dstports.split('-'))) == 1:
dstports = dstports.split('-')[0]
else:
dstports = None
if rule.get('specialtarget', None) == 'dns':
if dstports not in ('53', None):
continue
else:
dstports = '53'
if not dns:
continue
nft_rule += ' {} daddr {{ {} }}'.format(ip_match, ', '.join(
dns))
if 'icmptype' in rule:
if family == 4:
nft_rule += ' icmp type {}'.format(rule['icmptype'])
elif family == 6:
nft_rule += ' icmpv6 type {}'.format(rule['icmptype'])
# now duplicate rules for tcp/udp if needed
# it isn't possible to specify "tcp dport xx || udp dport xx" in
# one rule
if dstports is not None:
if 'proto' not in rule:
nft_rules.append(
nft_rule + ' tcp dport {} {}'.format(
dstports, rule['action']))
nft_rules.append(
nft_rule + ' udp dport {} {}'.format(
dstports, rule['action']))
else:
nft_rules.append(
nft_rule + ' {} dport {} {}'.format(
rule['proto'], dstports, rule['action']))
else:
nft_rules.append(nft_rule + ' ' + rule['action'])
return (
'flush chain {family} {table} {chain}\n'
'table {family} {table} {{\n'
' chain {chain} {{\n'
' {rules}\n'
' }}\n'
'}}\n'.format(
family=('ip6' if family == 6 else 'ip'),
table='qubes-firewall',
chain=chain,
rules='\n '.join(nft_rules)
))
def apply_rules_family(self, source, rules, family):
'''
Apply rules for given source address.
Handle only rules for given address family (IPv4 or IPv6).
:param source: source address
:param rules: rules list
:param family: address family, either 4 or 6
:return: None
'''
chain = self.chain_for_addr(source)
if chain not in self.chains[family]:
self.create_chain(source, chain, family)
self.run_nft(self.prepare_rules(chain, rules, family))
def apply_rules(self, source, rules):
if self.is_ip6(source):
self.apply_rules_family(source, rules, 6)
else:
self.apply_rules_family(source, rules, 4)
def init(self):
# make sure 'QBS_FORWARD' chain exists - should be created before
# starting qubes-firewall
nft_init = (
'table {family} qubes-firewall {{\n'
' chain forward {{\n'
' type filter hook forward priority 0;\n'
' }}\n'
'}}\n'
)
nft_init = ''.join(
nft_init.format(family=family) for family in ('ip', 'ip6'))
self.run_nft(nft_init)
def cleanup(self):
nft_cleanup = (
'delete table ip qubes-firewall\n'
'delete table ip6 qubes-firewall\n'
)
self.run_nft(nft_cleanup)
def main():
if spawn.find_executable('nft'):
worker = NftablesWorker()
else:
worker = IptablesWorker()
context = daemon.DaemonContext()
context.stderr = sys.stderr
context.detach_process = False
context.files_preserve = [worker.qdb.watch_fd()]
context.signal_map = {
signal.SIGTERM: lambda _signal, _stack: worker.terminate(),
}
with context:
worker.main()
if __name__ == '__main__':
main()

527
qubesagent/test_firewall.py Normal file
View File

@ -0,0 +1,527 @@
import logging
import operator
from unittest import TestCase
from unittest.mock import patch
import qubesagent.firewall
class DummyIptablesRestore(object):
# pylint: disable=too-few-public-methods
def __init__(self, worker_mock, family):
self._worker_mock = worker_mock
self._family = family
self.returncode = 0
def communicate(self, stdin=None):
self._worker_mock.loaded_iptables[self._family] = stdin
return ("", None)
class DummyQubesDB(object):
def __init__(self, worker_mock):
self._worker_mock = worker_mock
self.entries = {}
self.pending_watches = []
def read(self, key):
try:
return self.entries[key]
except KeyError:
return None
def multiread(self, prefix):
result = {}
for key, value in self.entries.items():
if key.startswith(prefix):
result[key] = value
return result
def list(self, prefix):
result = []
for key in self.entries.keys():
if key.startswith(prefix):
result.append(key)
return result
def watch(self, path):
pass
def read_watch(self):
try:
return self.pending_watches.pop(0)
except IndexError:
return None
class FirewallWorker(qubesagent.firewall.FirewallWorker):
def __init__(self):
# pylint: disable=super-init-not-called
# don't call super on purpose - avoid connecting to QubesDB
# super(FirewallWorker, self).__init__()
self.qdb = DummyQubesDB(self)
self.log = logging.getLogger('qubes.tests')
self.init_called = False
self.cleanup_called = False
self.rules = {}
def apply_rules(self, source_addr, rules):
self.rules[source_addr] = rules
def cleanup(self):
self.init_called = True
def init(self):
self.cleanup_called = True
class IptablesWorker(qubesagent.firewall.IptablesWorker):
'''Override methods actually modifying system state to only log what
would be done'''
def __init__(self):
# pylint: disable=super-init-not-called
# don't call super on purpose - avoid connecting to QubesDB
# super(IptablesWorker, self).__init__()
# copied __init__:
self.qdb = DummyQubesDB(self)
self.log = logging.getLogger('qubes.tests')
self.chains = {
4: set(),
6: set(),
}
#: instead of really running `iptables`, log what would be called
self.called_commands = {
4: [],
6: [],
}
#: rules that would be loaded with `iptables-restore`
self.loaded_iptables = {
4: None,
6: None,
}
def run_ipt(self, family, args, **kwargs):
self.called_commands[family].append(args)
def run_ipt_restore(self, family, args):
return DummyIptablesRestore(self, family)
@staticmethod
def dns_addresses(family=None):
if family == 4:
return ['1.1.1.1', '2.2.2.2']
else:
return ['2001::1', '2001::2']
class NftablesWorker(qubesagent.firewall.NftablesWorker):
'''Override methods actually modifying system state to only log what
would be done'''
def __init__(self):
# pylint: disable=super-init-not-called
# don't call super on purpose - avoid connecting to QubesDB
# super(IptablesWorker, self).__init__()
# copied __init__:
self.qdb = DummyQubesDB(self)
self.log = logging.getLogger('qubes.tests')
self.chains = {
4: set(),
6: set(),
}
#: instead of really running `nft`, log what would be loaded
#: rules that would be loaded with `nft`
self.loaded_rules = []
def run_nft(self, nft_input):
self.loaded_rules.append(nft_input)
@staticmethod
def dns_addresses(family=None):
if family == 4:
return ['1.1.1.1', '2.2.2.2']
else:
return ['2001::1', '2001::2']
class TestIptablesWorker(TestCase):
def setUp(self):
super(TestIptablesWorker, self).setUp()
self.obj = IptablesWorker()
self.subprocess_patch = patch('subprocess.call')
self.subprocess_mock = self.subprocess_patch.start()
def tearDown(self):
self.subprocess_patch.stop()
def test_000_chain_for_addr(self):
self.assertEqual(
self.obj.chain_for_addr('10.137.0.1'), 'qbs-10-137-0-1')
self.assertEqual(
self.obj.chain_for_addr('fd09:24ef:4179:0000::3'),
'qbs-fd09-24ef-4179-0000--3')
def test_001_create_chain(self):
testdata = [
(4, '10.137.0.1', 'qbs-10-137-0-1'),
(6, 'fd09:24ef:4179:0000::3', 'qbs-fd09-24ef-4179-0000--3')
]
for family, addr, chain in testdata:
self.obj.create_chain(addr, chain, family)
self.assertEqual(self.obj.called_commands[family],
[['-N', chain],
['-A', 'QBS-FORWARD', '-s', addr, '-j', chain]])
def test_002_prepare_rules4(self):
rules = [
{'action': 'accept', 'proto': 'tcp',
'dstports': '80-80', 'dst4': '1.2.3.0/24'},
{'action': 'accept', 'proto': 'udp',
'dstports': '443-1024', 'dsthost': 'yum.qubes-os.org'},
{'action': 'accept', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'udp', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'icmp'},
{'action': 'drop'},
]
expected_iptables = (
"*filter\n"
"-A chain -d 1.2.3.0/24 -p tcp --dport 80:80 -j ACCEPT\n"
"-A chain -d 82.94.215.165/32 -p udp --dport 443:1024 -j ACCEPT\n"
"-A chain -d 1.1.1.1/32 -p tcp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2.2.2.2/32 -p tcp --dport 53:53 -j ACCEPT\n"
"-A chain -d 1.1.1.1/32 -p udp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2.2.2.2/32 -p udp --dport 53:53 -j ACCEPT\n"
"-A chain -d 1.1.1.1/32 -p udp --dport 53:53 -j DROP\n"
"-A chain -d 2.2.2.2/32 -p udp --dport 53:53 -j DROP\n"
"-A chain -p icmp -j DROP\n"
"-A chain -j DROP\n"
"COMMIT\n"
)
self.assertEqual(self.obj.prepare_rules('chain', rules, 4),
expected_iptables)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'unknown': 'xxx'}], 4)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'dst6': 'a::b'}], 4)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'dst4': '3.3.3.3'}], 6)
def test_003_prepare_rules6(self):
rules = [
{'action': 'accept', 'proto': 'tcp',
'dstports': '80-80', 'dst6': 'a::b/128'},
{'action': 'accept', 'proto': 'tcp',
'dsthost': 'ripe.net'},
{'action': 'accept', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'udp', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'icmp'},
{'action': 'drop'},
]
expected_iptables = (
"*filter\n"
"-A chain -d a::b/128 -p tcp --dport 80:80 -j ACCEPT\n"
"-A chain -d 2001:67c:2e8:22::c100:68b/128 -p tcp -j ACCEPT\n"
"-A chain -d 2001::1/128 -p tcp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2001::2/128 -p tcp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2001::1/128 -p udp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2001::2/128 -p udp --dport 53:53 -j ACCEPT\n"
"-A chain -d 2001::1/128 -p udp --dport 53:53 -j DROP\n"
"-A chain -d 2001::2/128 -p udp --dport 53:53 -j DROP\n"
"-A chain -p icmp -j DROP\n"
"-A chain -j DROP\n"
"COMMIT\n"
)
self.assertEqual(self.obj.prepare_rules('chain', rules, 6),
expected_iptables)
def test_004_apply_rules4(self):
rules = [{'action': 'accept'}]
chain = 'qbs-10-137-0-1'
self.obj.apply_rules('10.137.0.1', rules)
self.assertEqual(self.obj.called_commands[4],
[
['-N', chain],
['-A', 'QBS-FORWARD', '-s', '10.137.0.1', '-j', chain],
['-F', chain]])
self.assertEqual(self.obj.loaded_iptables[4],
self.obj.prepare_rules(chain, rules, 4))
self.assertEqual(self.obj.called_commands[6], [])
self.assertIsNone(self.obj.loaded_iptables[6])
def test_005_apply_rules6(self):
rules = [{'action': 'accept'}]
chain = 'qbs-2000--a'
self.obj.apply_rules('2000::a', rules)
self.assertEqual(self.obj.called_commands[6],
[
['-N', chain],
['-A', 'QBS-FORWARD', '-s', '2000::a', '-j', chain],
['-F', chain]])
self.assertEqual(self.obj.loaded_iptables[6],
self.obj.prepare_rules(chain, rules, 6))
self.assertEqual(self.obj.called_commands[4], [])
self.assertIsNone(self.obj.loaded_iptables[4])
def test_006_init(self):
self.obj.init()
self.assertEqual(self.obj.called_commands[4],
[['-nL', 'QBS-FORWARD']])
self.assertEqual(self.obj.called_commands[6],
[['-nL', 'QBS-FORWARD']])
def test_007_cleanup(self):
self.obj.init()
self.obj.create_chain('1.2.3.4', 'chain-ip4-1', 4)
self.obj.create_chain('1.2.3.6', 'chain-ip4-2', 4)
self.obj.create_chain('2000::1', 'chain-ip6-1', 6)
self.obj.create_chain('2000::2', 'chain-ip6-2', 6)
# forget about commands called earlier
self.obj.called_commands[4] = []
self.obj.called_commands[6] = []
self.obj.cleanup()
self.assertEqual([self.obj.called_commands[4][0]] +
sorted(self.obj.called_commands[4][1:], key=operator.itemgetter(1)),
[['-F', 'QBS-FORWARD'],
['-F', 'chain-ip4-1'],
['-X', 'chain-ip4-1'],
['-F', 'chain-ip4-2'],
['-X', 'chain-ip4-2']])
self.assertEqual([self.obj.called_commands[6][0]] +
sorted(self.obj.called_commands[6][1:], key=operator.itemgetter(1)),
[['-F', 'QBS-FORWARD'],
['-F', 'chain-ip6-1'],
['-X', 'chain-ip6-1'],
['-F', 'chain-ip6-2'],
['-X', 'chain-ip6-2']])
class TestNftablesWorker(TestCase):
def setUp(self):
super(TestNftablesWorker, self).setUp()
self.obj = NftablesWorker()
self.subprocess_patch = patch('subprocess.call')
self.subprocess_mock = self.subprocess_patch.start()
def tearDown(self):
self.subprocess_patch.stop()
def test_000_chain_for_addr(self):
self.assertEqual(
self.obj.chain_for_addr('10.137.0.1'), 'qbs-10-137-0-1')
self.assertEqual(
self.obj.chain_for_addr('fd09:24ef:4179:0000::3'),
'qbs-fd09-24ef-4179-0000--3')
def expected_create_chain(self, family, addr, chain):
return (
'table {family} qubes-firewall {{\n'
' chain {chain} {{\n'
' }}\n'
' chain forward {{\n'
' {family} saddr {addr} jump {chain}\n'
' }}\n'
'}}\n'.format(family=family, addr=addr, chain=chain))
def test_001_create_chain(self):
testdata = [
(4, '10.137.0.1', 'qbs-10-137-0-1'),
(6, 'fd09:24ef:4179:0000::3', 'qbs-fd09-24ef-4179-0000--3')
]
for family, addr, chain in testdata:
self.obj.create_chain(addr, chain, family)
self.assertEqual(self.obj.loaded_rules,
[self.expected_create_chain('ip', '10.137.0.1', 'qbs-10-137-0-1'),
self.expected_create_chain(
'ip6', 'fd09:24ef:4179:0000::3', 'qbs-fd09-24ef-4179-0000--3'),
])
def test_002_prepare_rules4(self):
rules = [
{'action': 'accept', 'proto': 'tcp',
'dstports': '80-80', 'dst4': '1.2.3.0/24'},
{'action': 'accept', 'proto': 'udp',
'dstports': '443-1024', 'dsthost': 'yum.qubes-os.org'},
{'action': 'accept', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'udp', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'icmp'},
{'action': 'drop'},
]
expected_nft = (
'flush chain ip qubes-firewall chain\n'
'table ip qubes-firewall {\n'
' chain chain {\n'
' ip protocol tcp ip daddr 1.2.3.0/24 tcp dport 80 accept\n'
' ip protocol udp ip daddr { 82.94.215.165/32 } '
'udp dport 443-1024 accept\n'
' ip daddr { 1.1.1.1/32, 2.2.2.2/32 } tcp dport 53 accept\n'
' ip daddr { 1.1.1.1/32, 2.2.2.2/32 } udp dport 53 accept\n'
' ip protocol udp ip daddr { 1.1.1.1/32, 2.2.2.2/32 } udp dport '
'53 drop\n'
' ip protocol icmp drop\n'
' drop\n'
' }\n'
'}\n'
)
self.assertEqual(self.obj.prepare_rules('chain', rules, 4),
expected_nft)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'unknown': 'xxx'}], 4)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'dst6': 'a::b'}], 4)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.prepare_rules('chain', [{'dst4': '3.3.3.3'}], 6)
def test_003_prepare_rules6(self):
rules = [
{'action': 'accept', 'proto': 'tcp',
'dstports': '80-80', 'dst6': 'a::b/128'},
{'action': 'accept', 'proto': 'tcp',
'dsthost': 'ripe.net'},
{'action': 'accept', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'udp', 'specialtarget': 'dns'},
{'action': 'drop', 'proto': 'icmp', 'icmptype': '128'},
{'action': 'drop'},
]
expected_nft = (
'flush chain ip6 qubes-firewall chain\n'
'table ip6 qubes-firewall {\n'
' chain chain {\n'
' ip6 nexthdr tcp ip6 daddr a::b/128 tcp dport 80 accept\n'
' ip6 nexthdr tcp ip6 daddr { 2001:67c:2e8:22::c100:68b/128 } '
'accept\n'
' ip6 daddr { 2001::1/128, 2001::2/128 } tcp dport 53 accept\n'
' ip6 daddr { 2001::1/128, 2001::2/128 } udp dport 53 accept\n'
' ip6 nexthdr udp ip6 daddr { 2001::1/128, 2001::2/128 } '
'udp dport 53 drop\n'
' ip6 nexthdr icmpv6 icmpv6 type 128 drop\n'
' drop\n'
' }\n'
'}\n'
)
self.assertEqual(self.obj.prepare_rules('chain', rules, 6),
expected_nft)
def test_004_apply_rules4(self):
rules = [{'action': 'accept'}]
chain = 'qbs-10-137-0-1'
self.obj.apply_rules('10.137.0.1', rules)
self.assertEqual(self.obj.loaded_rules,
[self.expected_create_chain('ip', '10.137.0.1', chain),
self.obj.prepare_rules(chain, rules, 4),
])
def test_005_apply_rules6(self):
rules = [{'action': 'accept'}]
chain = 'qbs-2000--a'
self.obj.apply_rules('2000::a', rules)
self.assertEqual(self.obj.loaded_rules,
[self.expected_create_chain('ip6', '2000::a', chain),
self.obj.prepare_rules(chain, rules, 6),
])
def test_006_init(self):
self.obj.init()
self.assertEqual(self.obj.loaded_rules,
[
'table ip qubes-firewall {\n'
' chain forward {\n'
' type filter hook forward priority 0;\n'
' }\n'
'}\n'
'table ip6 qubes-firewall {\n'
' chain forward {\n'
' type filter hook forward priority 0;\n'
' }\n'
'}\n'
])
def test_007_cleanup(self):
self.obj.init()
self.obj.create_chain('1.2.3.4', 'chain-ip4-1', 4)
self.obj.create_chain('1.2.3.6', 'chain-ip4-2', 4)
self.obj.create_chain('2000::1', 'chain-ip6-1', 6)
self.obj.create_chain('2000::2', 'chain-ip6-2', 6)
# forget about commands called earlier
self.obj.loaded_rules = []
self.obj.cleanup()
self.assertEqual(self.obj.loaded_rules,
['delete table ip qubes-firewall\n'
'delete table ip6 qubes-firewall\n',
])
class TestFirewallWorker(TestCase):
def setUp(self):
self.obj = FirewallWorker()
rules = {
'10.137.0.1': {
'policy': 'accept',
'0000': 'proto=tcp dstports=80-80 action=drop',
'0001': 'proto=udp specialtarget=dns action=accept',
'0002': 'proto=udp action=drop',
},
'10.137.0.2': {'policy': 'accept'},
# no policy
'10.137.0.3': {'0000': 'proto=tcp action=accept'},
# no action
'10.137.0.4': {
'policy': 'drop',
'0000': 'proto=tcp'
},
}
for addr, entries in rules.items():
for key, value in entries.items():
self.obj.qdb.entries[
'/qubes-firewall/{}/{}'.format(addr, key)] = value
self.subprocess_patch = patch('subprocess.call')
self.subprocess_mock = self.subprocess_patch.start()
def tearDown(self):
self.subprocess_patch.stop()
def test_read_rules(self):
expected_rules1 = [
{'proto': 'tcp', 'dstports': '80-80', 'action': 'drop'},
{'proto': 'udp', 'specialtarget': 'dns', 'action': 'accept'},
{'proto': 'udp', 'action': 'drop'},
{'action': 'accept'},
]
expected_rules2 = [
{'action': 'accept'},
]
self.assertEqual(self.obj.read_rules('10.137.0.1'), expected_rules1)
self.assertEqual(self.obj.read_rules('10.137.0.2'), expected_rules2)
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.read_rules('10.137.0.3')
with self.assertRaises(qubesagent.firewall.RuleParseError):
self.obj.read_rules('10.137.0.4')
def test_list_targets(self):
self.assertEqual(self.obj.list_targets(), set(['10.137.0.{}'.format(x)
for x in range(1, 5)]))
def test_is_ip6(self):
self.assertTrue(self.obj.is_ip6('2000::abcd'))
self.assertTrue(self.obj.is_ip6('2000:1:2:3:4:5:6:abcd'))
self.assertFalse(self.obj.is_ip6('10.137.0.1'))
def test_handle_addr(self):
self.obj.handle_addr('10.137.0.2')
self.assertEqual(self.obj.rules['10.137.0.2'], [{'action': 'accept'}])
# fallback to block all
self.obj.handle_addr('10.137.0.3')
self.assertEqual(self.obj.rules['10.137.0.3'], [{'action': 'drop'}])
self.obj.handle_addr('10.137.0.4')
self.assertEqual(self.obj.rules['10.137.0.4'], [{'action': 'drop'}])
def test_main(self):
self.obj.main()
self.assertTrue(self.obj.init_called)
self.assertTrue(self.obj.cleanup_called)
self.assertEqual(set(self.obj.rules.keys()), self.obj.list_targets())
# rules content were already tested

View File

@ -20,7 +20,7 @@
# #
# #
%define qubes_services qubes-core qubes-core-netvm qubes-core-early qubes-firewall qubes-netwatcher qubes-iptables qubes-updates-proxy qubes-qrexec-agent qubes-dvm %define qubes_services qubes-core qubes-core-netvm qubes-core-early qubes-firewall qubes-iptables qubes-updates-proxy qubes-qrexec-agent qubes-dvm
%define qubes_preset_file 75-qubes-vm.preset %define qubes_preset_file 75-qubes-vm.preset
%{!?version: %define version %(cat version)} %{!?version: %define version %(cat version)}
@ -139,6 +139,8 @@ Requires: pygobject3-base
Requires: dbus-python Requires: dbus-python
# for qubes-session-autostart, xdg-icon # for qubes-session-autostart, xdg-icon
Requires: pyxdg Requires: pyxdg
Requires: python-daemon
Requires: nftables
Requires: ImageMagick Requires: ImageMagick
Requires: librsvg2-tools Requires: librsvg2-tools
Requires: fakeroot Requires: fakeroot
@ -157,6 +159,7 @@ Requires: python2-dnf-plugins-qubes-hooks
Obsoletes: qubes-core-vm-kernel-placeholder <= 1.0 Obsoletes: qubes-core-vm-kernel-placeholder <= 1.0
Obsoletes: qubes-upgrade-vm < 3.2 Obsoletes: qubes-upgrade-vm < 3.2
BuildRequires: xen-devel BuildRequires: xen-devel
BuildRequires: python3-devel
BuildRequires: libX11-devel BuildRequires: libX11-devel
BuildRequires: qubes-utils-devel >= 3.1.3 BuildRequires: qubes-utils-devel >= 3.1.3
BuildRequires: qubes-libvchan-%{backend_vmm}-devel BuildRequires: qubes-libvchan-%{backend_vmm}-devel
@ -430,6 +433,8 @@ rm -f %{name}-%{version}
%config(noreplace) /etc/qubes-rpc/qubes.GetImageRGBA %config(noreplace) /etc/qubes-rpc/qubes.GetImageRGBA
%config(noreplace) /etc/qubes-rpc/qubes.SetDateTime %config(noreplace) /etc/qubes-rpc/qubes.SetDateTime
%config(noreplace) /etc/qubes-rpc/qubes.InstallUpdatesGUI %config(noreplace) /etc/qubes-rpc/qubes.InstallUpdatesGUI
%config(noreplace) /etc/qubes-rpc/qubes.ResizeDisk
%config(noreplace) /etc/qubes-rpc/qubes.StartApp
%dir /etc/qubes/autostart %dir /etc/qubes/autostart
/etc/qubes/autostart/README.txt /etc/qubes/autostart/README.txt
%config /etc/qubes/autostart/*.desktop.d/30_qubes.conf %config /etc/qubes/autostart/*.desktop.d/30_qubes.conf
@ -449,6 +454,7 @@ rm -f %{name}-%{version}
%config(noreplace) /etc/qubes-suspend-module-blacklist %config(noreplace) /etc/qubes-suspend-module-blacklist
/etc/xdg/autostart/00-qubes-show-hide-nm-applet.desktop /etc/xdg/autostart/00-qubes-show-hide-nm-applet.desktop
/etc/xen/scripts/vif-route-qubes /etc/xen/scripts/vif-route-qubes
/etc/xen/scripts/vif-qubes-nat.sh
%config(noreplace) /etc/yum.conf.d/qubes-proxy.conf %config(noreplace) /etc/yum.conf.d/qubes-proxy.conf
%config(noreplace) /etc/yum.repos.d/qubes-r3.repo %config(noreplace) /etc/yum.repos.d/qubes-r3.repo
/etc/yum/pluginconf.d/yum-qubes-hooks.conf /etc/yum/pluginconf.d/yum-qubes-hooks.conf
@ -464,6 +470,7 @@ rm -f %{name}-%{version}
/usr/bin/qvm-open-in-vm /usr/bin/qvm-open-in-vm
/usr/bin/qvm-run /usr/bin/qvm-run
/usr/bin/qvm-mru-entry /usr/bin/qvm-mru-entry
/usr/bin/qvm-features-request
/usr/bin/xenstore-watch-qubes /usr/bin/xenstore-watch-qubes
/usr/bin/qubes-desktop-run /usr/bin/qubes-desktop-run
/usr/bin/qubes-open /usr/bin/qubes-open
@ -510,17 +517,25 @@ rm -f %{name}-%{version}
/usr/lib/qubes/init/functions /usr/lib/qubes/init/functions
%dir /usr/lib/qubes-bind-dirs.d %dir /usr/lib/qubes-bind-dirs.d
/usr/lib/qubes-bind-dirs.d/30_cron.conf /usr/lib/qubes-bind-dirs.d/30_cron.conf
/usr/lib64/python2.7/site-packages/qubes/xdg.py* /usr/lib/python2.7/site-packages/qubesxdg.py*
/usr/sbin/qubes-firewall /usr/sbin/qubes-firewall
/usr/sbin/qubes-netwatcher
/usr/share/qubes/serial.conf /usr/share/qubes/serial.conf
/usr/share/glib-2.0/schemas/org.gnome.settings-daemon.plugins.updates.gschema.override /usr/share/glib-2.0/schemas/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override
/usr/share/glib-2.0/schemas/org.gnome.nautilus.gschema.override /usr/share/glib-2.0/schemas/20_org.gnome.nautilus.qubes.gschema.override
/usr/share/glib-2.0/schemas/org.mate.NotificationDaemon.gschema.override /usr/share/glib-2.0/schemas/20_org.mate.NotificationDaemon.qubes.gschema.override
/usr/share/nautilus-python/extensions/qvm_copy_nautilus.py* /usr/share/nautilus-python/extensions/qvm_copy_nautilus.py*
/usr/share/nautilus-python/extensions/qvm_move_nautilus.py* /usr/share/nautilus-python/extensions/qvm_move_nautilus.py*
/usr/share/nautilus-python/extensions/qvm_dvm_nautilus.py* /usr/share/nautilus-python/extensions/qvm_dvm_nautilus.py*
%dir %{python3_sitelib}/qubesagent-*.egg-info
%{python3_sitelib}/qubesagent-*.egg-info/*
%dir %{python3_sitelib}/qubesagent
%dir %{python3_sitelib}/qubesagent/__pycache__
%{python3_sitelib}/qubesagent/__pycache__/*
%{python3_sitelib}/qubesagent/__init__.py
%{python3_sitelib}/qubesagent/firewall.py
%{python3_sitelib}/qubesagent/test_firewall.py
/usr/share/qubes/mime-override/globs /usr/share/qubes/mime-override/globs
/usr/share/qubes/qubes-master-key.asc /usr/share/qubes/qubes-master-key.asc
%dir /home_volatile %dir /home_volatile
@ -553,7 +568,6 @@ The Qubes core startup configuration for SysV init (or upstart).
/etc/init.d/qubes-dvm /etc/init.d/qubes-dvm
/etc/init.d/qubes-core-netvm /etc/init.d/qubes-core-netvm
/etc/init.d/qubes-firewall /etc/init.d/qubes-firewall
/etc/init.d/qubes-netwatcher
/etc/init.d/qubes-iptables /etc/init.d/qubes-iptables
/etc/init.d/qubes-updates-proxy /etc/init.d/qubes-updates-proxy
/etc/init.d/qubes-qrexec-agent /etc/init.d/qubes-qrexec-agent
@ -588,6 +602,9 @@ for svc in %qubes_services ; do
fi fi
done done
# dropped services
chkconfig qubes-netwatcher off || :
# TODO: make this not display the silly message about security context... # TODO: make this not display the silly message about security context...
sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab
@ -620,7 +637,6 @@ The Qubes core startup configuration for SystemD init.
/lib/systemd/system/qubes-misc-post.service /lib/systemd/system/qubes-misc-post.service
/lib/systemd/system/qubes-firewall.service /lib/systemd/system/qubes-firewall.service
/lib/systemd/system/qubes-mount-dirs.service /lib/systemd/system/qubes-mount-dirs.service
/lib/systemd/system/qubes-netwatcher.service
/lib/systemd/system/qubes-network.service /lib/systemd/system/qubes-network.service
/lib/systemd/system/qubes-iptables.service /lib/systemd/system/qubes-iptables.service
/lib/systemd/system/qubes-sysinit.service /lib/systemd/system/qubes-sysinit.service

13
run-tests Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
: "${PYTHON:=python3}"
: "${ROOTDIR:=.}"
: "${TESTPYTHONPATH:=$ROOTDIR/test-packages}"
PYTHONPATH="${TESTPYTHONPATH}:${PYTHONPATH}"
export PYTHONPATH
[ -r version ] || ln -s ${ROOTDIR}/version ./
[ -r setup.py ] || ln -s ${ROOTDIR}/setup.py ./
"${PYTHON}" ./setup.py egg_info --egg-base "${TESTPYTHONPATH}"
"${PYTHON}" -m coverage run -m unittest discover -p '*.py' -v "$@"

22
setup.py Normal file
View File

@ -0,0 +1,22 @@
# vim: fileencoding=utf-8
import setuptools
if __name__ == '__main__':
setuptools.setup(
name='qubesagent',
version=open('version').read().strip(),
author='Invisible Things Lab',
author_email='marmarek@invisiblethingslab.com',
description='Qubes core-agent-linux package',
license='GPL2+',
url='https://www.qubes-os.org/',
packages=('qubesagent',),
entry_points={
'console_scripts': [
'qubes-firewall = qubesagent.firewall:main'
],
}
)

0
test-packages/qubesdb.py Normal file
View File

View File

@ -1,50 +0,0 @@
#!/bin/bash
#
# chkconfig: 345 93 93
# description: Starts Qubes Network monitor
#
# Source function library.
. /etc/rc.d/init.d/functions
# Source Qubes library.
. /usr/lib/qubes/init/functions
PIDFILE=/var/run/qubes/qubes-netwatcher.pid
start()
{
have_qubesdb || return
if qsvc qubes-netwatcher ; then
echo -n $"Starting Qubes Network monitor:"
/sbin/ethtool -K eth0 sg off
/usr/sbin/qubes-netwatcher &
success
echo ""
fi
}
stop()
{
if [ -r "$PIDFILE" ]; then
echo -n "Stopping Qubes Network monitor:"
kill -9 $(cat $PIDFILE) 2>/dev/null && success || failure
echo ""
fi
return 0
}
case "$1" in
start)
start
;;
stop)
stop
;;
*)
echo $"Usage: $0 {start|stop}"
exit 3
;;
esac
exit $RETVAL

View File

@ -88,7 +88,6 @@ enable qubes-network.service
enable qubes-qrexec-agent.service enable qubes-qrexec-agent.service
enable qubes-mount-dirs.service enable qubes-mount-dirs.service
enable qubes-firewall.service enable qubes-firewall.service
enable qubes-netwatcher.service
enable qubes-meminfo-writer.service enable qubes-meminfo-writer.service
enable qubes-iptables.service enable qubes-iptables.service
enable haveged.service enable haveged.service

View File

@ -1,10 +0,0 @@
[Unit]
Description=Qubes network monitor
ConditionPathExists=/var/run/qubes-service/qubes-netwatcher
After=network-pre.target qubes-firewall.service
[Service]
ExecStart=/usr/sbin/qubes-netwatcher
[Install]
WantedBy=multi-user.target

View File

@ -5,10 +5,20 @@
# List of services enabled by default (in case of absence of qubesdb entry) # List of services enabled by default (in case of absence of qubesdb entry)
DEFAULT_ENABLED_NETVM="network-manager qubes-network qubes-update-check qubes-updates-proxy" DEFAULT_ENABLED_NETVM="network-manager qubes-network qubes-update-check qubes-updates-proxy"
DEFAULT_ENABLED_PROXYVM="meminfo-writer qubes-network qubes-firewall qubes-netwatcher qubes-update-check" DEFAULT_ENABLED_PROXYVM="qubes-network qubes-firewall qubes-update-check"
DEFAULT_ENABLED_APPVM="meminfo-writer cups qubes-update-check" DEFAULT_ENABLED_APPVM="cups qubes-update-check"
DEFAULT_ENABLED_TEMPLATEVM="$DEFAULT_ENABLED_APPVM updates-proxy-setup" DEFAULT_ENABLED_TEMPLATEVM="$DEFAULT_ENABLED_APPVM updates-proxy-setup"
DEFAULT_ENABLED="meminfo-writer" DEFAULT_ENABLED=""
if [ -z "`ls /sys/bus/pci/devices/`" ]; then
# do not enable meminfo-writer (so qmemman for this domain) when any PCI
# device is present
DEFAULT_ENABLED="$DEFAULT_ENABLED meminfo-writer"
DEFAULT_ENABLED_APPVM="$DEFAULT_ENABLED_APPVM meminfo-writer"
DEFAULT_ENABLED_PROXYVM="$DEFAULT_ENABLED_PROXYVM meminfo-writer"
DEFAULT_ENABLED_TEMPLATEVM="$DEFAULT_ENABLED_TEMPLATEVM meminfo-writer"
fi
if systemd_version_changed ; then if systemd_version_changed ; then
# Ensure we're running right version of systemd (the one started by initrd may be different) # Ensure we're running right version of systemd (the one started by initrd may be different)