From 2a15863ccb7b2931d5a248d9db97907ec8a38886 Mon Sep 17 00:00:00 2001 From: qubesuser Date: Sun, 30 Aug 2015 16:27:14 +0200 Subject: [PATCH 01/20] network: add vif-route-qubes-nat for IP address anonymization --- Makefile | 2 + network/vif-qubes-nat.sh | 79 ++++++++++++++++++++++++++++++++ network/vif-route-qubes-nat | 91 +++++++++++++++++++++++++++++++++++++ rpm_spec/core-vm.spec | 2 + 4 files changed, 174 insertions(+) create mode 100755 network/vif-qubes-nat.sh create mode 100755 network/vif-route-qubes-nat diff --git a/Makefile b/Makefile index 1af2899..81d7a26 100644 --- a/Makefile +++ b/Makefile @@ -156,6 +156,8 @@ install-common: install -d $(DESTDIR)/etc/NetworkManager/dispatcher.d/ install network/{qubes-nmhook,30-qubes-external-ip} $(DESTDIR)/etc/NetworkManager/dispatcher.d/ install -D network/vif-route-qubes $(DESTDIR)/etc/xen/scripts/vif-route-qubes + install -D network/vif-route-qubes-nat $(DESTDIR)/etc/xen/scripts/vif-route-qubes-nat + 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/filter-updates $(DESTDIR)/etc/tinyproxy/filter-updates install -m 0755 -D network/iptables-updates-proxy $(DESTDIR)$(LIBDIR)/qubes/iptables-updates-proxy diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh new file mode 100755 index 0000000..9e26845 --- /dev/null +++ b/network/vif-qubes-nat.sh @@ -0,0 +1,79 @@ +#!/bin/bash +#set -x + +netvm_subnet=/24 +undetectable_netvm_ips=1 + +netns="${vif}-nat" +netvm_if="${vif}" +netns_netvm_if="${vif}-p" +netns_appvm_if="${vif}" + +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" + + run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" + 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_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_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$netvm_subnet" 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 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 + diff --git a/network/vif-route-qubes-nat b/network/vif-route-qubes-nat new file mode 100755 index 0000000..4a232bc --- /dev/null +++ b/network/vif-route-qubes-nat @@ -0,0 +1,91 @@ +#!/bin/bash +#============================================================================ +# /etc/xen/vif-route-qubes-nat +# +# Script for configuring a vif in routed mode. +# The hotplugging system will call this script if it is specified either in +# the device configuration given to Xend, or the default Xend configuration +# in /etc/xen/xend-config.sxp. If the script is specified in neither of those +# places, then vif-bridge is the default. +# +# Usage: +# vif-route (add|remove|online|offline) +# +# Environment vars: +# vif vif interface name (required). +# XENBUS_PATH path to this device's details in the XenStore (required). +# +# Read from the store: +# ip list of IP networks for the vif, space-separated (default given in +# this script). +#============================================================================ + +appvm_gw_ip="$1" +netvm_ip="$2" +shift 2 + +dir=$(dirname "$0") +. "$dir/vif-common.sh" + +if [ "${ip}" ]; then + appvm_ip="$ip" + netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway` + netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns` + + ip="$netvm_ip" + back_ip="$netvm_gw_ip" +fi + +#echo "$appvm_ip $appvm_gw_ip $netvm_ip $netvm_gw_ip" >> /var/log/qubes-nat.log + +#main_ip=$(dom0_ip) +lockfile=/var/run/xen-hotplug/vif-lock + +if [ "${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 + online) + ifconfig ${vif} up + echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp + ipcmd='add' + iptables_cmd='-I PREROUTING 1' + cmdprefix='' + ;; + offline) + do_without_error ifdown ${vif} + ipcmd='del' + iptables_cmd='-D PREROUTING' + cmdprefix='do_without_error' + ;; +esac + +domid=${vif/vif/} +domid=${domid/.*/} +# metric must be possitive, but prefer later interface +# 32752 is max XID aka domid +metric=$[ 32752 - $domid ] + +if [ "${ip}" ] ; then + # If we've been given a list of IP addresses, then add routes from dom0 to + # the guest using those addresses. + for addr in ${ip} ; do + ${cmdprefix} ip route ${ipcmd} ${addr} dev ${vif} metric $metric + done + echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ + ${cmdprefix} flock $lockfile iptables-restore --noflush + ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} +fi + +log debug "Successful vif-route-qubes-nat $command for $vif." +if [ "$command" = "online" ] +then + # disable tx checksumming offload, apparently it doesn't work with our ancient qemu in stubdom + do_without_error ethtool -K $vif tx off + success +fi diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 21a968f..bc6106e 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -367,6 +367,8 @@ rm -f %{name}-%{version} %config(noreplace) /etc/udev/rules.d/99-qubes-network.rules /etc/xdg/autostart/00-qubes-show-hide-nm-applet.desktop /etc/xen/scripts/vif-route-qubes +/etc/xen/scripts/vif-route-qubes-nat +/etc/xen/scripts/vif-qubes-nat.sh %config(noreplace) /etc/yum.conf.d/qubes-proxy.conf %config(noreplace) /etc/yum.repos.d/qubes-r3.repo /etc/yum/pluginconf.d/yum-qubes-hooks.conf From 5261f936b292482449436d59376d65a1832914ee Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Thu, 19 May 2016 01:36:31 +0200 Subject: [PATCH 02/20] misc: add qvm-features-request This tool is used to request features from template. QubesOS/qubes-issues#1637 --- Makefile | 14 ++++--- misc/qvm-features-request | 81 +++++++++++++++++++++++++++++++++++++++ rpm_spec/core-vm.spec | 1 + 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100755 misc/qvm-features-request diff --git a/Makefile b/Makefile index 06fda31..48a9dae 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ VERSION := $(shell cat version) DIST ?= fc18 KDESERVICEDIR ?= /usr/share/kde4/services SBINDIR ?= /usr/sbin +BINDIR ?= /usr/bin LIBDIR ?= /usr/lib SYSLIBDIR ?= /lib @@ -149,7 +150,7 @@ install-common: 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 -m 0644 misc/udev-qubes-misc.rules $(DESTDIR)/etc/udev/rules.d/50-qubes-misc.rules install -d $(DESTDIR)$(LIBDIR)/qubes/ @@ -199,17 +200,18 @@ install-common: install network/qubes-firewall $(DESTDIR)/$(SBINDIR)/ 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 -d $(DESTDIR)$(BINDIR) + install -m 0755 misc/qubes-session-autostart $(DESTDIR)$(BINDIR)/qubes-session-autostart + install -m 0755 misc/qvm-features-request $(DESTDIR)$(BINDIR)/qvm-features-request + install qubes-rpc/{qvm-open-in-dvm,qvm-open-in-vm,qvm-copy-to-vm,qvm-move-to-vm,qvm-run,qvm-mru-entry} $(DESTDIR)$(BINDIR) - install qubes-rpc/{qvm-open-in-dvm,qvm-open-in-vm,qvm-copy-to-vm,qvm-move-to-vm,qvm-run,qvm-mru-entry} $(DESTDIR)/usr/bin install qubes-rpc/qvm-copy-to-vm.kde $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/qvm-copy-to-vm.gnome $(DESTDIR)$(LIBDIR)/qubes install qubes-rpc/qvm-move-to-vm.kde $(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/{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 qfile-unpacker as SUID - because it will fail to receive files from other vm install -m 4755 qubes-rpc/qfile-unpacker $(DESTDIR)$(LIBDIR)/qubes @@ -241,7 +243,7 @@ install-common: install -d $(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/ diff --git a/misc/qvm-features-request b/misc/qvm-features-request new file mode 100755 index 0000000..9fcc61b --- /dev/null +++ b/misc/qvm-features-request @@ -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 +# Copyright (C) 2016 Wojtek Porczyk +# +# 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()) diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 3b51da5..11e2fc0 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -384,6 +384,7 @@ rm -f %{name}-%{version} /usr/bin/qvm-open-in-vm /usr/bin/qvm-run /usr/bin/qvm-mru-entry +/usr/bin/qvm-features-request /usr/bin/xenstore-watch-qubes /usr/bin/qubes-desktop-run /usr/bin/qubes-open From 76e12cae2d91e3e9491487ff343be462ae3d63ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 14 Mar 2016 16:06:52 +0100 Subject: [PATCH 03/20] Rename qubes.xdg python module to qubesxdg Do not interfere with 'qubes' module. QubesOS/qubes-issues#1813 --- Makefile | 4 ++-- misc/Makefile | 2 +- misc/qubes-desktop-run | 2 +- misc/qubes-session-autostart | 4 ++-- misc/{xdg.py => qubesxdg.py} | 0 rpm_spec/core-vm.spec | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename misc/{xdg.py => qubesxdg.py} (100%) diff --git a/Makefile b/Makefile index 85afde2..a72df19 100644 --- a/Makefile +++ b/Makefile @@ -249,9 +249,9 @@ install-common: mkdir -p $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ 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 - install -m 0644 misc/py2/xdg.py* $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/ + install -m 0644 misc/py2/qubesxdg.py* $(DESTDIR)/$(PYTHON2_SITELIB)/ endif install -d $(DESTDIR)/mnt/removable diff --git a/misc/Makefile b/misc/Makefile index 932f636..01533c3 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -23,7 +23,7 @@ python2: python3: rm -rf 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 -O -m compileall py3 diff --git a/misc/qubes-desktop-run b/misc/qubes-desktop-run index eb79c93..e1116fb 100755 --- a/misc/qubes-desktop-run +++ b/misc/qubes-desktop-run @@ -1,6 +1,6 @@ #!/usr/bin/python -from qubes.xdg import launch +from qubesxdg import launch import sys if __name__ == '__main__': diff --git a/misc/qubes-session-autostart b/misc/qubes-session-autostart index fcf3439..e7bfe11 100644 --- a/misc/qubes-session-autostart +++ b/misc/qubes-session-autostart @@ -25,7 +25,7 @@ import subprocess import sys from xdg.DesktopEntry import DesktopEntry -from qubes.xdg import launch +from qubesxdg import launch import xdg.BaseDirectory import os @@ -86,4 +86,4 @@ def main(): process_autostart(sys.argv[1:]) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/misc/xdg.py b/misc/qubesxdg.py similarity index 100% rename from misc/xdg.py rename to misc/qubesxdg.py diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 88929c0..75377ae 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -424,7 +424,7 @@ rm -f %{name}-%{version} /usr/lib/qubes/upgrades-status-notify /usr/lib/yum-plugins/yum-qubes-hooks.py* /usr/lib/dracut/dracut.conf.d/30-qubes.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-netwatcher /usr/share/qubes/serial.conf From c3d630f28878aa62bd94d471d0cee14f12792a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 21 Jun 2016 19:08:16 +0200 Subject: [PATCH 04/20] Disable meminfo-writer if there is any PCI device attached This code used to be in dom0, but it's easier to maintain it in VM. QubesOS/qubes-issues#2101 --- vm-systemd/qubes-sysinit.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/vm-systemd/qubes-sysinit.sh b/vm-systemd/qubes-sysinit.sh index a46ac18..503dfa9 100755 --- a/vm-systemd/qubes-sysinit.sh +++ b/vm-systemd/qubes-sysinit.sh @@ -2,10 +2,20 @@ # 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_PROXYVM="meminfo-writer qubes-network qubes-firewall qubes-netwatcher qubes-update-check" -DEFAULT_ENABLED_APPVM="meminfo-writer cups qubes-update-check" +DEFAULT_ENABLED_PROXYVM="qubes-network qubes-firewall qubes-netwatcher qubes-update-check" +DEFAULT_ENABLED_APPVM="cups qubes-update-check" 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 + QDB_READ=qubesdb-read QDB_LS=qubesdb-multiread From b50cba3f2cf8185ad515be164e1107faf42a8672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 17 Aug 2016 21:47:22 +0200 Subject: [PATCH 05/20] Add qubes.ResizeDisk service to adjust filesystem size Do this using qubes rpc service, instead of calling resize2fs directly by dom0. --- Makefile | 1 + qubes-rpc/qubes.ResizeDisk | 32 ++++++++++++++++++++++++++++++++ rpm_spec/core-vm.spec | 1 + 3 files changed, 34 insertions(+) create mode 100755 qubes-rpc/qubes.ResizeDisk diff --git a/Makefile b/Makefile index 42ab249..67af96d 100644 --- a/Makefile +++ b/Makefile @@ -237,6 +237,7 @@ install-common: install -m 0644 qubes-rpc/qubes.GetImageRGBA $(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.ResizeDisk $(DESTDIR)/etc/qubes-rpc install -d $(DESTDIR)/etc/qubes/suspend-pre.d install -m 0644 qubes-rpc/suspend-pre.README $(DESTDIR)/etc/qubes/suspend-pre.d/README diff --git a/qubes-rpc/qubes.ResizeDisk b/qubes-rpc/qubes.ResizeDisk new file mode 100755 index 0000000..a1ad8b1 --- /dev/null +++ b/qubes-rpc/qubes.ResizeDisk @@ -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 + diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index bca5102..8c6fcb8 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -350,6 +350,7 @@ rm -f %{name}-%{version} %config(noreplace) /etc/qubes-rpc/qubes.GetImageRGBA %config(noreplace) /etc/qubes-rpc/qubes.SetDateTime %config(noreplace) /etc/qubes-rpc/qubes.InstallUpdatesGUI +%config(noreplace) /etc/qubes-rpc/qubes.ResizeDisk %dir /etc/qubes/autostart /etc/qubes/autostart/README.txt %config /etc/qubes/autostart/*.desktop.d/30_qubes.conf From ee0a292b21c4ce8c364b67adf6c2b3a3470cdef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 12 Sep 2016 05:22:53 +0200 Subject: [PATCH 06/20] network: rewrite qubes-firewall daemon This rewrite is mainly to adopt new interface for Qubes 4.x. Main changes: - change language from bash to python, introduce qubesagent python package - support both nftables (preferred) and iptables - new interface (https://qubes-os.org/doc/vm-interface/) - IPv6 support - unit tests included - nftables version support running along with other firewall loaded Fixes QubesOS/qubes-issues#1815 QubesOS/qubes-issues#718 --- Makefile | 6 +- debian/control | 1 + network/ip6tables | 2 + network/iptables | 2 + network/qubes-firewall | 58 ---- qubesagent/__init__.py | 0 qubesagent/firewall.py | 576 ++++++++++++++++++++++++++++++++++++ qubesagent/test_firewall.py | 507 +++++++++++++++++++++++++++++++ rpm_spec/core-vm.spec | 9 + setup.py | 22 ++ 10 files changed, 1124 insertions(+), 59 deletions(-) delete mode 100755 network/qubes-firewall create mode 100644 qubesagent/__init__.py create mode 100755 qubesagent/firewall.py create mode 100644 qubesagent/test_firewall.py create mode 100644 setup.py diff --git a/Makefile b/Makefile index 67af96d..f31b28b 100644 --- a/Makefile +++ b/Makefile @@ -146,6 +146,11 @@ install-common: $(MAKE) -C autostart-dropins install 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)" python setup.py install -O1 --root $(DESTDIR) + mkdir -p $(DESTDIR)$(SBINDIR) + mv $(DESTDIR)/usr/bin/qubes-firewall $(DESTDIR)$(SBINDIR)/qubes-firewall + 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 0644 misc/20_tcp_timestamps.conf $(DESTDIR)/etc/sysctl.d/20_tcp_timestamps.conf @@ -200,7 +205,6 @@ install-common: install -d $(DESTDIR)/$(SBINDIR) - install network/qubes-firewall $(DESTDIR)/$(SBINDIR)/ install network/qubes-netwatcher $(DESTDIR)/$(SBINDIR)/ install -d $(DESTDIR)$(BINDIR) diff --git a/debian/control b/debian/control index 866913c..a54cbcc 100644 --- a/debian/control +++ b/debian/control @@ -38,6 +38,7 @@ Depends: net-tools, psmisc, python2.7, + python-daemon, python-gi, python-xdg, python-dbus, diff --git a/network/ip6tables b/network/ip6tables index 8a906f5..4fd5d90 100644 --- a/network/ip6tables +++ b/network/ip6tables @@ -3,6 +3,8 @@ :INPUT DROP [1:72] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] +:QBS-FORWARD - [0:0] -A INPUT -i lo -j ACCEPT +-A FORWARD -j QBS-FORWARD COMMIT # Completed on Tue Sep 25 16:00:20 2012 diff --git a/network/iptables b/network/iptables index 51e652c..16f560b 100644 --- a/network/iptables +++ b/network/iptables @@ -17,6 +17,7 @@ COMMIT :INPUT ACCEPT [168:11399] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [128:12536] +:QBS-FORWARD - [0:0] -A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -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 -j DROP -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+ -j ACCEPT -A FORWARD -j DROP diff --git a/network/qubes-firewall b/network/qubes-firewall deleted file mode 100755 index 5c1bfe9..0000000 --- a/network/qubes-firewall +++ /dev/null @@ -1,58 +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 - /usr/bin/qubesdb-watch $XENSTORE_IPTABLES - TRIGGER=$(/usr/bin/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 - - # 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 diff --git a/qubesagent/__init__.py b/qubesagent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qubesagent/firewall.py b/qubesagent/firewall.py new file mode 100755 index 0000000..ca9a036 --- /dev/null +++ b/qubesagent/firewall.py @@ -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 +# +# 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() diff --git a/qubesagent/test_firewall.py b/qubesagent/test_firewall.py new file mode 100644 index 0000000..55612ce --- /dev/null +++ b/qubesagent/test_firewall.py @@ -0,0 +1,507 @@ +import logging +from unittest import TestCase + +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() + + 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], + [['-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], + [['-F', 'QBS-FORWARD'], + ['-F', 'chain-ip6-2'], + ['-X', 'chain-ip6-2'], + ['-F', 'chain-ip6-1'], + ['-X', 'chain-ip6-1']]) + + +class TestNftablesWorker(TestCase): + def setUp(self): + super(TestNftablesWorker, self).setUp() + self.obj = NftablesWorker() + + 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 + + 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 diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 8c6fcb8..e888fd6 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -54,6 +54,8 @@ Requires: pygobject3-base Requires: dbus-python # for qubes-session-autostart, xdg-icon Requires: pyxdg +Requires: python-daemon +Requires: nftables %if %{fedora} >= 20 # gpk-update-viewer required by qubes-manager Requires: gnome-packagekit-updater @@ -437,6 +439,13 @@ rm -f %{name}-%{version} /usr/share/nautilus-python/extensions/qvm_move_nautilus.py* /usr/share/nautilus-python/extensions/qvm_dvm_nautilus.py* +%dir %{python_sitelib}/qubesagent-*-py2.7.egg-info +%{python_sitelib}/qubesagent-*-py2.7.egg-info/* +%dir %{python_sitelib}/qubesagent +%{python_sitelib}/qubesagent/__init__.py* +%{python_sitelib}/qubesagent/firewall.py* +%{python_sitelib}/qubesagent/test_firewall.py* + /usr/share/qubes/mime-override/globs /usr/share/qubes/qubes-master-key.asc %dir /home_volatile diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2c0f163 --- /dev/null +++ b/setup.py @@ -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' + ], + } + ) From 2c8fe644f3f95a2901e6687854f419ebe517c81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 12 Sep 2016 05:31:02 +0200 Subject: [PATCH 07/20] network: remove qubes-netwatcher This tool/service is obsolete for a long time (it does nothing on R3.0 and later). --- Makefile | 5 --- archlinux/PKGBUILD | 1 - archlinux/PKGBUILD.install | 4 +-- debian/qubes-core-agent.postrm | 2 +- network/qubes-netwatcher | 31 ------------------- rpm_spec/core-vm.spec | 16 +++++----- vm-init.d/qubes-netwatcher | 48 ----------------------------- vm-systemd/75-qubes-vm.preset | 1 - vm-systemd/qubes-netwatcher.service | 11 ------- vm-systemd/qubes-sysinit.sh | 2 +- 10 files changed, 12 insertions(+), 109 deletions(-) delete mode 100755 network/qubes-netwatcher delete mode 100755 vm-init.d/qubes-netwatcher delete mode 100644 vm-systemd/qubes-netwatcher.service diff --git a/Makefile b/Makefile index f31b28b..77f02e9 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,6 @@ install-sysvinit: install vm-init.d/qubes-core-appvm $(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-netwatcher $(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 -D vm-init.d/qubes-core.modules $(DESTDIR)/etc/sysconfig/modules/qubes-core.modules @@ -203,10 +202,6 @@ install-common: install -m 0400 -D network/ip6tables $(DESTDIR)/etc/qubes/ip6tables.rules install -m 0755 network/update-proxy-configs $(DESTDIR)$(LIBDIR)/qubes/ - - install -d $(DESTDIR)/$(SBINDIR) - install network/qubes-netwatcher $(DESTDIR)/$(SBINDIR)/ - install -d $(DESTDIR)$(BINDIR) install -m 0755 misc/qubes-session-autostart $(DESTDIR)$(BINDIR)/qubes-session-autostart install -m 0755 misc/qvm-features-request $(DESTDIR)$(BINDIR)/qvm-features-request diff --git a/archlinux/PKGBUILD b/archlinux/PKGBUILD index d315827..b51d604 100644 --- a/archlinux/PKGBUILD +++ b/archlinux/PKGBUILD @@ -60,7 +60,6 @@ sed 's:#!/usr/bin/env python:#!/usr/bin/env python2:' -i qubes-rpc/* # Fix for archlinux sbindir 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 for dir in qubes-rpc qrexec misc; do diff --git a/archlinux/PKGBUILD.install b/archlinux/PKGBUILD.install index 4af2b3f..dff2c4d 100644 --- a/archlinux/PKGBUILD.install +++ b/archlinux/PKGBUILD.install @@ -158,7 +158,7 @@ if [ $1 -eq 1 ]; then systemctl --no-reload preset-all 2>&1 && PRESET_FAILED=0 || PRESET_FAILED=1 else services="qubes-dvm qubes-misc-post qubes-firewall qubes-mount-dirs" - services="$services qubes-netwatcher qubes-network qubes-sysinit" + services="$services qubes-network qubes-sysinit" services="$services qubes-iptables qubes-updates-proxy qubes-qrexec-agent" services="$services qubes-random-seed" for srv in $services; do @@ -357,7 +357,7 @@ post_remove() { 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 done diff --git a/debian/qubes-core-agent.postrm b/debian/qubes-core-agent.postrm index 91dcc21..c18702d 100755 --- a/debian/qubes-core-agent.postrm +++ b/debian/qubes-core-agent.postrm @@ -43,7 +43,7 @@ if [ "${1}" = "remove" ] ; then rm /lib/firmware/updates 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 done fi diff --git a/network/qubes-netwatcher b/network/qubes-netwatcher deleted file mode 100755 index 3cd46be..0000000 --- a/network/qubes-netwatcher +++ /dev/null @@ -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 diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index e888fd6..c38bca9 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -430,7 +430,6 @@ rm -f %{name}-%{version} /usr/lib/dracut/dracut.conf.d/30-qubes.conf /usr/lib/python2.7/site-packages/qubesxdg.py* /usr/sbin/qubes-firewall -/usr/sbin/qubes-netwatcher /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/org.gnome.nautilus.gschema.override @@ -476,7 +475,6 @@ The Qubes core startup configuration for SysV init (or upstart). /etc/init.d/qubes-core-appvm /etc/init.d/qubes-core-netvm /etc/init.d/qubes-firewall -/etc/init.d/qubes-netwatcher /etc/init.d/qubes-iptables /etc/init.d/qubes-updates-proxy /etc/init.d/qubes-qrexec-agent @@ -511,8 +509,6 @@ chkconfig --add qubes-core-appvm || echo "WARNING: Cannot add service qubes-core chkconfig qubes-core-appvm on || echo "WARNING: Cannot enable service qubes-core-appvm!" chkconfig --add qubes-firewall || echo "WARNING: Cannot add service qubes-firewall!" chkconfig qubes-firewall on || echo "WARNING: Cannot enable service qubes-firewall!" -chkconfig --add qubes-netwatcher || echo "WARNING: Cannot add service qubes-netwatcher!" -chkconfig qubes-netwatcher on || echo "WARNING: Cannot enable service qubes-netwatcher!" chkconfig --add qubes-iptables || echo "WARNING: Cannot add service qubes-iptables!" chkconfig qubes-iptables on || echo "WARNING: Cannot enable service qubes-iptables!" chkconfig --add qubes-updates-proxy || echo "WARNING: Cannot add service qubes-updates-proxy!" @@ -520,6 +516,9 @@ chkconfig qubes-updates-proxy on || echo "WARNING: Cannot enable service qubes-u chkconfig --add qubes-qrexec-agent || echo "WARNING: Cannot add service qubes-qrexec-agent!" chkconfig qubes-qrexec-agent on || echo "WARNING: Cannot enable service qubes-qrexec-agent!" +# dropped services +chkconfig qubes-netwatcher off || : + # TODO: make this not display the silly message about security context... sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab @@ -530,7 +529,6 @@ if [ "$1" = 0 ] ; then chkconfig qubes-core-netvm off chkconfig qubes-core-appvm off chkconfig qubes-firewall off - chkconfig qubes-netwatcher off chkconfig qubes-updates-proxy off chkconfig qubes-qrexec-agent off fi @@ -556,7 +554,6 @@ The Qubes core startup configuration for SystemD init. /lib/systemd/system/qubes-misc-post.service /lib/systemd/system/qubes-firewall.service /lib/systemd/system/qubes-mount-dirs.service -/lib/systemd/system/qubes-netwatcher.service /lib/systemd/system/qubes-network.service /lib/systemd/system/qubes-iptables.service /lib/systemd/system/qubes-sysinit.service @@ -606,7 +603,7 @@ if [ $1 -eq 1 ]; then /bin/systemctl --no-reload preset-all > /dev/null 2>&1 && PRESET_FAILED=0 || PRESET_FAILED=1 else services="qubes-dvm qubes-misc-post qubes-firewall qubes-mount-dirs" - services="$services qubes-netwatcher qubes-network qubes-sysinit" + services="$services qubes-network qubes-sysinit" services="$services qubes-iptables qubes-updates-proxy qubes-qrexec-agent" for srv in $services; do /bin/systemctl --no-reload preset $srv.service @@ -621,6 +618,9 @@ else fi fi +# dropped services +/bin/systemctl disable qubes-netwatcher.service >/dev/null 2>&1 || : + # Set default "runlevel" rm -f /etc/systemd/system/default.target ln -s /lib/systemd/system/multi-user.target /etc/systemd/system/default.target @@ -663,6 +663,6 @@ if [ "$1" != 0 ] ; then exit 0 fi -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 /bin/systemctl disable $srv.service do diff --git a/vm-init.d/qubes-netwatcher b/vm-init.d/qubes-netwatcher deleted file mode 100755 index 316c271..0000000 --- a/vm-init.d/qubes-netwatcher +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# -# chkconfig: 345 92 92 -# description: Starts Qubes Network monitor -# -# Source function library. -. /etc/rc.d/init.d/functions - -PIDFILE=/var/run/qubes/qubes-netwatcher.pid - -start() -{ - type=$(/usr/bin/qubesdb-read /qubes-vm-type) - start_netwatcher=$(/usr/bin/qubesdb-read /qubes-service/qubes-netwatcher 2>/dev/null) - if [ -z "$start_netwatcher" ] && [ "$type" == "ProxyVM" ] || [ "$start_netwatcher" == "1" ]; then - echo -n $"Starting Qubes Network monitor:" - /sbin/ethtool -K eth0 sg off - /usr/sbin/qubes-netwatcher & - success - echo "" - fi - return 0 -} - -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 diff --git a/vm-systemd/75-qubes-vm.preset b/vm-systemd/75-qubes-vm.preset index 54dbcfa..aed47f3 100644 --- a/vm-systemd/75-qubes-vm.preset +++ b/vm-systemd/75-qubes-vm.preset @@ -68,7 +68,6 @@ enable qubes-network.service enable qubes-qrexec-agent.service enable qubes-mount-dirs.service enable qubes-firewall.service -enable qubes-netwatcher.service enable qubes-meminfo-writer.service enable qubes-iptables.service enable haveged.service diff --git a/vm-systemd/qubes-netwatcher.service b/vm-systemd/qubes-netwatcher.service deleted file mode 100644 index e25359d..0000000 --- a/vm-systemd/qubes-netwatcher.service +++ /dev/null @@ -1,11 +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 -StandardOutput=syslog - -[Install] -WantedBy=multi-user.target diff --git a/vm-systemd/qubes-sysinit.sh b/vm-systemd/qubes-sysinit.sh index 503dfa9..b135b35 100755 --- a/vm-systemd/qubes-sysinit.sh +++ b/vm-systemd/qubes-sysinit.sh @@ -2,7 +2,7 @@ # 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_PROXYVM="qubes-network qubes-firewall qubes-netwatcher qubes-update-check" +DEFAULT_ENABLED_PROXYVM="qubes-network qubes-firewall qubes-update-check" DEFAULT_ENABLED_APPVM="cups qubes-update-check" DEFAULT_ENABLED_TEMPLATEVM="$DEFAULT_ENABLED_APPVM updates-proxy-setup" DEFAULT_ENABLED="" From be86c7da1f177483f68a25db738dee89217a09dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 29 Oct 2016 14:45:36 +0200 Subject: [PATCH 08/20] network: reformat vif-route-qubes-nat Use 4-space indentation, remove trailing spaces. No functional change. --- network/vif-qubes-nat.sh | 74 ++++++++++++------------- network/vif-route-qubes-nat | 104 ++++++++++++++++++------------------ 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh index 9e26845..360e72e 100755 --- a/network/vif-qubes-nat.sh +++ b/network/vif-qubes-nat.sh @@ -11,13 +11,13 @@ netns_appvm_if="${vif}" function run { - #echo "$@" >> /var/log/qubes-nat.log - "$@" + #echo "$@" >> /var/log/qubes-nat.log + "$@" } function netns { - run ip netns exec "$netns" "$@" + run ip netns exec "$netns" "$@" } @@ -26,54 +26,54 @@ 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" + run ip netns add "$netns" + run ip link set "$netns_appvm_if" netns "$netns" - run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" - run ip link set "$netns_netvm_if" netns "$netns" + run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" + 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 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 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 + 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 + 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_dns2_ip" -j DROP - fi + # 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_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_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" + 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_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 + 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$netvm_subnet" dev "$netns_netvm_if" - netns ip addr add "$appvm_gw_ip" dev "$netns_appvm_if" + netns ip addr add "$netvm_ip$netvm_subnet" 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 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 default via "$netvm_gw_ip" dev "$netns_netvm_if" src "$netvm_ip" + netns ip route add "$appvm_ip" dev "$netns_appvm_if" src "$appvm_gw_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" + #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 diff --git a/network/vif-route-qubes-nat b/network/vif-route-qubes-nat index 4a232bc..2566bb0 100755 --- a/network/vif-route-qubes-nat +++ b/network/vif-route-qubes-nat @@ -1,24 +1,24 @@ -#!/bin/bash +#!/bin/bash #============================================================================ -# /etc/xen/vif-route-qubes-nat -# -# Script for configuring a vif in routed mode. -# The hotplugging system will call this script if it is specified either in -# the device configuration given to Xend, or the default Xend configuration +# /etc/xen/vif-route-qubes-nat +# +# Script for configuring a vif in routed mode. +# The hotplugging system will call this script if it is specified either in +# the device configuration given to Xend, or the default Xend configuration # in /etc/xen/xend-config.sxp. If the script is specified in neither of those -# places, then vif-bridge is the default. -# -# Usage: -# vif-route (add|remove|online|offline) -# -# Environment vars: -# vif vif interface name (required). -# XENBUS_PATH path to this device's details in the XenStore (required). -# -# Read from the store: -# ip list of IP networks for the vif, space-separated (default given in -# this script). -#============================================================================ +# places, then vif-bridge is the default. +# +# Usage: +# vif-route (add|remove|online|offline) +# +# Environment vars: +# vif vif interface name (required). +# XENBUS_PATH path to this device's details in the XenStore (required). +# +# Read from the store: +# ip list of IP networks for the vif, space-separated (default given in +# this script). +#============================================================================ appvm_gw_ip="$1" netvm_ip="$2" @@ -28,12 +28,12 @@ dir=$(dirname "$0") . "$dir/vif-common.sh" if [ "${ip}" ]; then - appvm_ip="$ip" - netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway` - netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns` + appvm_ip="$ip" + netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway` + netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns` - ip="$netvm_ip" - back_ip="$netvm_gw_ip" + ip="$netvm_ip" + back_ip="$netvm_gw_ip" fi #echo "$appvm_ip $appvm_gw_ip $netvm_ip $netvm_gw_ip" >> /var/log/qubes-nat.log @@ -42,27 +42,27 @@ fi lockfile=/var/run/xen-hotplug/vif-lock if [ "${ip}" ]; then - if test "$command" == online; then - echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp - fi + if test "$command" == online; then + echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp + fi - . "$dir/vif-qubes-nat.sh" + . "$dir/vif-qubes-nat.sh" fi case "$command" in - online) - ifconfig ${vif} up - echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp - ipcmd='add' - iptables_cmd='-I PREROUTING 1' - cmdprefix='' - ;; - offline) - do_without_error ifdown ${vif} - ipcmd='del' - iptables_cmd='-D PREROUTING' - cmdprefix='do_without_error' - ;; + online) + ifconfig ${vif} up + echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp + ipcmd='add' + iptables_cmd='-I PREROUTING 1' + cmdprefix='' + ;; + offline) + do_without_error ifdown ${vif} + ipcmd='del' + iptables_cmd='-D PREROUTING' + cmdprefix='do_without_error' + ;; esac domid=${vif/vif/} @@ -72,20 +72,20 @@ domid=${domid/.*/} metric=$[ 32752 - $domid ] if [ "${ip}" ] ; then - # If we've been given a list of IP addresses, then add routes from dom0 to - # the guest using those addresses. - for addr in ${ip} ; do - ${cmdprefix} ip route ${ipcmd} ${addr} dev ${vif} metric $metric - done - echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ - ${cmdprefix} flock $lockfile iptables-restore --noflush - ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} + # If we've been given a list of IP addresses, then add routes from dom0 to + # the guest using those addresses. + for addr in ${ip} ; do + ${cmdprefix} ip route ${ipcmd} ${addr} dev ${vif} metric $metric + done + echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ + ${cmdprefix} flock $lockfile iptables-restore --noflush + ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} fi log debug "Successful vif-route-qubes-nat $command for $vif." if [ "$command" = "online" ] then - # disable tx checksumming offload, apparently it doesn't work with our ancient qemu in stubdom - do_without_error ethtool -K $vif tx off - success + # disable tx checksumming offload, apparently it doesn't work with our ancient qemu in stubdom + do_without_error ethtool -K $vif tx off + success fi From 938af2c7fdae05c8e165dee259e17df9fad80880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 29 Oct 2016 22:28:57 +0200 Subject: [PATCH 09/20] network: change vif-route-qubes-nat parameters Keep "main" IP (the one in xenstore) as the one seen by the netvm, and pass the "fake" one (the one seen by the VM) as script parameter. Fixes QubesOS/qubes-issues#1143 --- network/vif-qubes-nat.sh | 21 ++++++++++++++++++--- network/vif-route-qubes-nat | 6 ++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh index 360e72e..57f588a 100755 --- a/network/vif-qubes-nat.sh +++ b/network/vif-qubes-nat.sh @@ -9,6 +9,24 @@ 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 @@ -20,8 +38,6 @@ function netns run ip netns exec "$netns" "$@" } - - run ip addr flush dev "$netns_appvm_if" run ip netns delete "$netns" || : @@ -32,7 +48,6 @@ if test "$command" == online; then run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" 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 diff --git a/network/vif-route-qubes-nat b/network/vif-route-qubes-nat index 2566bb0..c7da437 100755 --- a/network/vif-route-qubes-nat +++ b/network/vif-route-qubes-nat @@ -20,15 +20,17 @@ # this script). #============================================================================ +# IPs as seen by the VM appvm_gw_ip="$1" -netvm_ip="$2" +appvm_ip="$2" shift 2 dir=$(dirname "$0") . "$dir/vif-common.sh" if [ "${ip}" ]; then - appvm_ip="$ip" + # IPs as seen by this VM + netvm_ip="$ip" netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway` netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns` From 1c42a0623801cff072283ea99ba773d6538510ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 30 Oct 2016 21:41:35 +0100 Subject: [PATCH 10/20] network: integrate vif-route-qubes-nat into vif-route-qubes Since 'script' xenstore entry no longer allows passing arguments (actually this always was a side effect, not intended behaviour), we need to pass additional parameters some other way. Natural choice for Qubes-specific script is to use QubesDB. And since those parameters are passed some other way, it is no longer necessary to keep it as separate script. Fixes QubesOS/qubes-issues#1143 --- Makefile | 1 - network/vif-route-qubes | 25 +++++++++- network/vif-route-qubes-nat | 93 ------------------------------------- rpm_spec/core-vm.spec | 1 - 4 files changed, 24 insertions(+), 96 deletions(-) delete mode 100755 network/vif-route-qubes-nat diff --git a/Makefile b/Makefile index 3cfe06a..d048cbd 100644 --- a/Makefile +++ b/Makefile @@ -192,7 +192,6 @@ install-common: install -d $(DESTDIR)/etc/NetworkManager/dispatcher.d/ install network/{qubes-nmhook,30-qubes-external-ip} $(DESTDIR)/etc/NetworkManager/dispatcher.d/ install -D network/vif-route-qubes $(DESTDIR)/etc/xen/scripts/vif-route-qubes - install -D network/vif-route-qubes-nat $(DESTDIR)/etc/xen/scripts/vif-route-qubes-nat 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/updates-blacklist $(DESTDIR)/etc/tinyproxy/updates-blacklist diff --git a/network/vif-route-qubes b/network/vif-route-qubes index fdfd1e8..85da9f9 100755 --- a/network/vif-route-qubes +++ b/network/vif-route-qubes @@ -26,6 +26,30 @@ dir=$(dirname "$0") #main_ip=$(dom0_ip) 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_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 online) ifconfig ${vif} up @@ -55,7 +79,6 @@ if [ "${ip}" ] ; then done echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ ${cmdprefix} flock $lockfile iptables-restore --noflush - back_ip=`qubesdb-read /qubes-netvm-gateway` ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} fi diff --git a/network/vif-route-qubes-nat b/network/vif-route-qubes-nat deleted file mode 100755 index c7da437..0000000 --- a/network/vif-route-qubes-nat +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash -#============================================================================ -# /etc/xen/vif-route-qubes-nat -# -# Script for configuring a vif in routed mode. -# The hotplugging system will call this script if it is specified either in -# the device configuration given to Xend, or the default Xend configuration -# in /etc/xen/xend-config.sxp. If the script is specified in neither of those -# places, then vif-bridge is the default. -# -# Usage: -# vif-route (add|remove|online|offline) -# -# Environment vars: -# vif vif interface name (required). -# XENBUS_PATH path to this device's details in the XenStore (required). -# -# Read from the store: -# ip list of IP networks for the vif, space-separated (default given in -# this script). -#============================================================================ - -# IPs as seen by the VM -appvm_gw_ip="$1" -appvm_ip="$2" -shift 2 - -dir=$(dirname "$0") -. "$dir/vif-common.sh" - -if [ "${ip}" ]; then - # IPs as seen by this VM - netvm_ip="$ip" - netvm_gw_ip=`qubesdb-read /qubes-netvm-gateway` - netvm_dns2_ip=`qubesdb-read /qubes-netvm-secondary-dns` - - ip="$netvm_ip" - back_ip="$netvm_gw_ip" -fi - -#echo "$appvm_ip $appvm_gw_ip $netvm_ip $netvm_gw_ip" >> /var/log/qubes-nat.log - -#main_ip=$(dom0_ip) -lockfile=/var/run/xen-hotplug/vif-lock - -if [ "${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 - online) - ifconfig ${vif} up - echo 1 >/proc/sys/net/ipv4/conf/${vif}/proxy_arp - ipcmd='add' - iptables_cmd='-I PREROUTING 1' - cmdprefix='' - ;; - offline) - do_without_error ifdown ${vif} - ipcmd='del' - iptables_cmd='-D PREROUTING' - cmdprefix='do_without_error' - ;; -esac - -domid=${vif/vif/} -domid=${domid/.*/} -# metric must be possitive, but prefer later interface -# 32752 is max XID aka domid -metric=$[ 32752 - $domid ] - -if [ "${ip}" ] ; then - # If we've been given a list of IP addresses, then add routes from dom0 to - # the guest using those addresses. - for addr in ${ip} ; do - ${cmdprefix} ip route ${ipcmd} ${addr} dev ${vif} metric $metric - done - echo -e "*raw\n$iptables_cmd -i ${vif} ! -s ${ip} -j DROP\nCOMMIT" | \ - ${cmdprefix} flock $lockfile iptables-restore --noflush - ${cmdprefix} ip addr ${ipcmd} ${back_ip}/32 dev ${vif} -fi - -log debug "Successful vif-route-qubes-nat $command for $vif." -if [ "$command" = "online" ] -then - # disable tx checksumming offload, apparently it doesn't work with our ancient qemu in stubdom - do_without_error ethtool -K $vif tx off - success -fi diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 2bf4b60..7b54562 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -372,7 +372,6 @@ rm -f %{name}-%{version} %config(noreplace) /etc/qubes-suspend-module-blacklist /etc/xdg/autostart/00-qubes-show-hide-nm-applet.desktop /etc/xen/scripts/vif-route-qubes -/etc/xen/scripts/vif-route-qubes-nat /etc/xen/scripts/vif-qubes-nat.sh %config(noreplace) /etc/yum.conf.d/qubes-proxy.conf %config(noreplace) /etc/yum.repos.d/qubes-r3.repo From c75b6519c590f0f51e04fbefb215ca364fa1636b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 31 Oct 2016 00:55:59 +0100 Subject: [PATCH 11/20] network: keep the same MAC on vif interfaces Even when it's veth pair into network namespace doing NAT. QubesOS/qubes-issues#1143 --- network/vif-qubes-nat.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh index 57f588a..d422d8e 100755 --- a/network/vif-qubes-nat.sh +++ b/network/vif-qubes-nat.sh @@ -45,7 +45,9 @@ if test "$command" == online; then run ip netns add "$netns" run ip link set "$netns_appvm_if" netns "$netns" - run ip link add "$netns_netvm_if" type veth peer name "$netvm_if" + # 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 From c8213ea55ab1ef656c365c070668b9ddedb639bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 1 Nov 2016 00:14:46 +0100 Subject: [PATCH 12/20] network: properly handle DNS addresses in vif-qubes-nat.sh Core3 no longer reuse netvm own IP for primary DNS. At the same time, disable dropping traffic to netvm itself because it breaks DNS (as one of blocked things). This allows VM to learn real netvm IP, but: - this mechanism is not intended to avoid detection from already compromised VM, only about unintentional leaks - this can be prevented using vif-qubes-nat.sh on the netvm itself (so it will also have hidden its own IP) QubesOS/qubes-issues#1143 --- network/vif-qubes-nat.sh | 8 +++++++- network/vif-route-qubes | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh index d422d8e..2fea819 100755 --- a/network/vif-qubes-nat.sh +++ b/network/vif-qubes-nat.sh @@ -2,7 +2,7 @@ #set -x netvm_subnet=/24 -undetectable_netvm_ips=1 +undetectable_netvm_ips= netns="${vif}-nat" netvm_if="${vif}" @@ -65,6 +65,7 @@ if test "$command" == online; then # 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 @@ -74,6 +75,11 @@ if test "$command" == online; then 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" diff --git a/network/vif-route-qubes b/network/vif-route-qubes index 85da9f9..64ad86a 100755 --- a/network/vif-route-qubes +++ b/network/vif-route-qubes @@ -30,6 +30,7 @@ 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" From 24b726a3bf6751cf0368615e3ec5a4140f1f4ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 1 Nov 2016 00:22:19 +0100 Subject: [PATCH 13/20] network: use /32 netmask on internal IPs in NAT providing namespace Use /32 inside network namespace too. Otherwise inter-VM traffic is broken - as all VMs seems to be in a single /24 subnet, but in fact are not. QubesOS/qubes-issues#1143 --- network/vif-qubes-nat.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/vif-qubes-nat.sh b/network/vif-qubes-nat.sh index 2fea819..7a0a620 100755 --- a/network/vif-qubes-nat.sh +++ b/network/vif-qubes-nat.sh @@ -1,7 +1,6 @@ #!/bin/bash #set -x -netvm_subnet=/24 undetectable_netvm_ips= netns="${vif}-nat" @@ -85,13 +84,14 @@ if test "$command" == online; then 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$netvm_subnet" dev "$netns_netvm_if" + 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" From 41cd218660534e5a93086dde115370277cc09145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 4 Dec 2016 21:28:13 +0100 Subject: [PATCH 14/20] travis: drop debootstrap workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move to qubes-builder Signed-off-by: Marek Marczykowski-Górecki --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4cd3ed3..f644ced 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ sudo: required dist: trusty language: generic install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder -# debootstrap in trusty is old... -before_script: sudo ln -s sid /usr/share/debootstrap/scripts/stretch script: ~/qubes-builder/scripts/travis-build env: - DISTS_VM=fc23 USE_QUBES_REPO_VERSION=3.2 USE_QUBES_REPO_TESTING=1 From 22e261f9095f2d6f4f1b67533c20b9a0e18459a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 03:48:02 +0200 Subject: [PATCH 15/20] Add qubes.StartApp service A simple service to start application described in .desktop file. This way, dom0 can completely ignore VM-originated Exec= entry. --- Makefile | 1 + qubes-rpc/qubes.StartApp | 24 ++++++++++++++++++++++++ rpm_spec/core-vm.spec | 1 + 3 files changed, 26 insertions(+) create mode 100755 qubes-rpc/qubes.StartApp diff --git a/Makefile b/Makefile index d048cbd..208d1d1 100644 --- a/Makefile +++ b/Makefile @@ -238,6 +238,7 @@ install-common: 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.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 -m 0644 qubes-rpc/suspend-pre.README $(DESTDIR)/etc/qubes/suspend-pre.d/README diff --git a/qubes-rpc/qubes.StartApp b/qubes-rpc/qubes.StartApp new file mode 100755 index 0000000..ca3c48b --- /dev/null +++ b/qubes-rpc/qubes.StartApp @@ -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 diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 7b54562..d1afe00 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -353,6 +353,7 @@ rm -f %{name}-%{version} %config(noreplace) /etc/qubes-rpc/qubes.SetDateTime %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 /etc/qubes/autostart/README.txt %config /etc/qubes/autostart/*.desktop.d/30_qubes.conf From dc8047c3bb6f51be488126d9df378b3ed3b50bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 03:49:13 +0200 Subject: [PATCH 16/20] dom0-updates: restructure the script to not update metadata twice When `qubes-dom0-update --refresh` was called, the script checked metadata twice - once to check updates availability, then to actually download them. This two stage approach is needed only on Debian, when --downloadonly option is not supported. Rearrange code accordingly. Also, drop --doit option (ignore it), as the same (but more readable) can be achieved with --check-only. --- misc/qubes-download-dom0-updates.sh | 51 ++++++++++++----------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/misc/qubes-download-dom0-updates.sh b/misc/qubes-download-dom0-updates.sh index 08c537a..33de94b 100755 --- a/misc/qubes-download-dom0-updates.sh +++ b/misc/qubes-download-dom0-updates.sh @@ -2,7 +2,6 @@ DOM0_UPDATES_DIR=/var/lib/qubes/dom0-updates -DOIT=0 GUI=1 CLEAN=0 CHECK_ONLY=0 @@ -17,7 +16,7 @@ export LC_ALL=C while [ -n "$1" ]; do case "$1" in --doit) - DOIT=1 + # ignore ;; --nogui) GUI=0 @@ -80,45 +79,30 @@ if [ "$CLEAN" = "1" ]; then rm -rf $DOM0_UPDATES_DIR/var/cache/yum/* 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 UPDATES_FULL=`$YUM $OPTS check-update` check_update_retcode=$? - UPDATES_FULL=`echo "$UPDATES_FULL" | grep -v "^Loaded plugins:\|^$"` if [ $check_update_retcode -eq 1 ]; then # 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 exit 1 fi - UPDATES=`echo "$UPDATES_FULL" | grep -v "^Obsoleting\|Could not" | cut -f 1 -d ' '` - if [ -z "$UPDATES" -a $check_update_retcode -eq 100 ]; then - # save not empty string for below condition (-z "$UPDATES"), but blank - # to not confuse the user wwith magic strings in messages - UPDATES=" " + if [ $check_update_retcode -eq 100 ]; then + echo "Available updates: " + echo "$UPDATES_FULL" + exit 100 + else + echo "No new updates available" + if [ "$GUI" = 1 ]; then + zenity --info --text="No new updates available" + fi + exit 0 fi -else - PKGS_FROM_CMDLINE=1 -fi - -if [ -z "$PKGLIST" -a -z "$UPDATES" ]; then - echo "No new updates available" - if [ "$GUI" = 1 ]; then - zenity --info --text="No new updates available" - fi - exit 0 -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 +# now, we will download something YUM_COMMAND="fakeroot $YUM $YUM_ACTION -y --downloadonly" # check for --downloadonly option - if not supported (Debian), fallback to # yumdownloader @@ -132,6 +116,13 @@ if ! $YUM --help | grep -q downloadonly; then exit 1 fi if [ "$YUM_ACTION" = "upgrade" ]; then + UPDATES_FULL=`$YUM $OPTS check-update $PKGLIST` + check_update_retcode=$? + UPDATES=`echo "$UPDATES_FULL" | grep -v "^Obsoleting\|Could not" | cut -f 1 -d ' '` + if [ -z "$UPDATES" -a $check_update_retcode -eq 0 ]; then + echo "No new updates available" + exit 0 + fi PKGLIST=$UPDATES fi YUM_COMMAND="yumdownloader --destdir=$DOM0_UPDATES_DIR/packages --resolve" From 87efe51be0cf15bd7c7b47ccf66f1a33d352b0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 12:56:23 +0200 Subject: [PATCH 17/20] tests: make firewall tests working regardless of python version Don't depend on set ordering... --- qubesagent/test_firewall.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qubesagent/test_firewall.py b/qubesagent/test_firewall.py index 55612ce..b6222c6 100644 --- a/qubesagent/test_firewall.py +++ b/qubesagent/test_firewall.py @@ -1,4 +1,5 @@ import logging +import operator from unittest import TestCase import qubesagent.firewall @@ -275,18 +276,20 @@ class TestIptablesWorker(TestCase): self.obj.called_commands[4] = [] self.obj.called_commands[6] = [] self.obj.cleanup() - self.assertEqual(self.obj.called_commands[4], + 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], + 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-2'], - ['-X', 'chain-ip6-2'], ['-F', 'chain-ip6-1'], - ['-X', 'chain-ip6-1']]) + ['-X', 'chain-ip6-1'], + ['-F', 'chain-ip6-2'], + ['-X', 'chain-ip6-2']]) class TestNftablesWorker(TestCase): From 5dfcf06ef4b5ae9889dddb26b30849146fa0557c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 13:19:51 +0200 Subject: [PATCH 18/20] firewall: switch to python 3 --- Makefile | 2 +- debian/control | 1 + rpm_spec/core-vm.spec | 15 +++++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 208d1d1..a82b74a 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,7 @@ install-common: 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)" python setup.py install -O1 --root $(DESTDIR) + 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 diff --git a/debian/control b/debian/control index a54cbcc..47fd103 100644 --- a/debian/control +++ b/debian/control @@ -7,6 +7,7 @@ Build-Depends: libqubes-rpc-filecopy-dev (>= 3.1.3), libvchan-xen-dev, python, + python3-setuptools, debhelper, quilt, libxen-dev, diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index d1afe00..f0af167 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -78,6 +78,7 @@ Requires: python2-dnf-plugins-qubes-hooks Obsoletes: qubes-core-vm-kernel-placeholder <= 1.0 Obsoletes: qubes-upgrade-vm < 3.2 BuildRequires: xen-devel +BuildRequires: python3-devel BuildRequires: libX11-devel BuildRequires: qubes-utils-devel >= 3.1.3 BuildRequires: qubes-libvchan-%{backend_vmm}-devel @@ -440,12 +441,14 @@ rm -f %{name}-%{version} /usr/share/nautilus-python/extensions/qvm_move_nautilus.py* /usr/share/nautilus-python/extensions/qvm_dvm_nautilus.py* -%dir %{python_sitelib}/qubesagent-*-py2.7.egg-info -%{python_sitelib}/qubesagent-*-py2.7.egg-info/* -%dir %{python_sitelib}/qubesagent -%{python_sitelib}/qubesagent/__init__.py* -%{python_sitelib}/qubesagent/firewall.py* -%{python_sitelib}/qubesagent/test_firewall.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/qubes-master-key.asc From 07be216a0d45e368a0c2019d43bc8dd904a8cf1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 13:05:54 +0200 Subject: [PATCH 19/20] tests: add run-tests script, plug it into travis Also, replace subproces.call with a mockup, as notify-send is not available on travis. --- .coveragerc | 3 +++ .gitignore | 2 ++ .travis.yml | 22 +++++++++++++++++++++- ci/requirements.txt | 6 ++++++ qubesagent/test_firewall.py | 17 +++++++++++++++++ run-tests | 13 +++++++++++++ test-packages/qubesdb.py | 0 7 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .coveragerc create mode 100644 ci/requirements.txt create mode 100755 run-tests create mode 100644 test-packages/qubesdb.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..bcaddf3 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = qubesagent +omit = qubesagent/test* diff --git a/.gitignore b/.gitignore index 557a3f0..cc81005 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ deb/* *.pyo *~ *.o +.coverage +*.egg-info diff --git a/.travis.yml b/.travis.yml index f644ced..e465bb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ sudo: required dist: trusty -language: generic +language: python +python: '3.5' install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder script: ~/qubes-builder/scripts/travis-build env: @@ -8,3 +9,22 @@ env: - 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=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: + - /.*_.*/ diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..b5abd8d --- /dev/null +++ b/ci/requirements.txt @@ -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 diff --git a/qubesagent/test_firewall.py b/qubesagent/test_firewall.py index b6222c6..f373122 100644 --- a/qubesagent/test_firewall.py +++ b/qubesagent/test_firewall.py @@ -1,6 +1,7 @@ import logging import operator from unittest import TestCase +from unittest.mock import patch import qubesagent.firewall @@ -150,6 +151,11 @@ 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( @@ -296,6 +302,11 @@ 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( @@ -465,6 +476,12 @@ class TestFirewallWorker(TestCase): 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'}, diff --git a/run-tests b/run-tests new file mode 100755 index 0000000..00e7497 --- /dev/null +++ b/run-tests @@ -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 "$@" diff --git a/test-packages/qubesdb.py b/test-packages/qubesdb.py new file mode 100644 index 0000000..e69de29 From abf9a5aa43a9fbbaa05a03c77e27b85a6104ae59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 20 May 2017 13:38:58 +0200 Subject: [PATCH 20/20] Apply gschema overrides also to debian, rename according to guidelines glib-compile-schemas recommend naming override files with nn_ prefix, where nn is a number. Lets use 20, to allow both higher and lower priority files. QubesOS/qubes-issues#1108 --- Makefile | 10 ++++++---- ...de => 20_org.gnome.nautilus.qubes.gschema.override} | 0 ...ings-daemon.plugins.updates.qubes.gschema.override} | 0 ...org.mate.NotificationDaemon.qubes.gschema.override} | 0 rpm_spec/core-vm.spec | 6 +++--- 5 files changed, 9 insertions(+), 7 deletions(-) rename misc/{org.gnome.nautilus.gschema.override => 20_org.gnome.nautilus.qubes.gschema.override} (100%) rename misc/{org.gnome.settings-daemon.plugins.updates.gschema.override => 20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override} (100%) rename misc/{org.mate.NotificationDaemon.gschema.override => 20_org.mate.NotificationDaemon.qubes.gschema.override} (100%) diff --git a/Makefile b/Makefile index a82b74a..ed21ae4 100644 --- a/Makefile +++ b/Makefile @@ -113,10 +113,6 @@ 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 $(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 -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 @@ -166,6 +162,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/mime-globs $(DESTDIR)/usr/share/qubes/mime-override/globs 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 -D -m 0644 misc/qubes-master-key.asc $(DESTDIR)/usr/share/qubes/qubes-master-key.asc diff --git a/misc/org.gnome.nautilus.gschema.override b/misc/20_org.gnome.nautilus.qubes.gschema.override similarity index 100% rename from misc/org.gnome.nautilus.gschema.override rename to misc/20_org.gnome.nautilus.qubes.gschema.override diff --git a/misc/org.gnome.settings-daemon.plugins.updates.gschema.override b/misc/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override similarity index 100% rename from misc/org.gnome.settings-daemon.plugins.updates.gschema.override rename to misc/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override diff --git a/misc/org.mate.NotificationDaemon.gschema.override b/misc/20_org.mate.NotificationDaemon.qubes.gschema.override similarity index 100% rename from misc/org.mate.NotificationDaemon.gschema.override rename to misc/20_org.mate.NotificationDaemon.qubes.gschema.override diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index f0af167..45aff06 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -434,9 +434,9 @@ rm -f %{name}-%{version} /usr/lib/python2.7/site-packages/qubesxdg.py* /usr/sbin/qubes-firewall /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/org.gnome.nautilus.gschema.override -/usr/share/glib-2.0/schemas/org.mate.NotificationDaemon.gschema.override +/usr/share/glib-2.0/schemas/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override +/usr/share/glib-2.0/schemas/20_org.gnome.nautilus.qubes.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_move_nautilus.py* /usr/share/nautilus-python/extensions/qvm_dvm_nautilus.py*