From 7ad6b816701cc47c5c236036065de96434f5f11c Mon Sep 17 00:00:00 2001 From: "Manuel Amador (Rudd-O)" Date: Tue, 11 Oct 2016 19:06:09 +0000 Subject: [PATCH] Initial commit. --- .gitignore | 4 + Makefile | 20 + README.md | 121 ++++++ doc/Qubes network server model.dia | Bin 0 -> 2141 bytes doc/Qubes network server model.png | Bin 0 -> 9570 bytes doc/Standard Qubes OS network model.dia | Bin 0 -> 2360 bytes doc/Standard Qubes OS network model.png | Bin 0 -> 10226 bytes qubes-network-server.spec | 36 ++ src/usr/bin/qvm-static-ip | 170 ++++++++ .../qubes/modules/001FortressQubesVm.py | 401 ++++++++++++++++++ .../qubes/modules/006FortressQubesNetVm.py | 46 ++ .../qubes/modules/007FortressQubesProxyVm.py | 183 ++++++++ 12 files changed, 981 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 doc/Qubes network server model.dia create mode 100644 doc/Qubes network server model.png create mode 100644 doc/Standard Qubes OS network model.dia create mode 100644 doc/Standard Qubes OS network model.png create mode 100644 qubes-network-server.spec create mode 100644 src/usr/bin/qvm-static-ip create mode 100644 src/usr/lib64/python2.7/site-packages/qubes/modules/001FortressQubesVm.py create mode 100644 src/usr/lib64/python2.7/site-packages/qubes/modules/006FortressQubesNetVm.py create mode 100644 src/usr/lib64/python2.7/site-packages/qubes/modules/007FortressQubesProxyVm.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d02285 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*~ +*.tar.gz +*.rpm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b2d178 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +BINDIR=/usr/bin +LIBDIR=/usr/lib64 +DESTDIR= + +clean: + find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f + rm -f *.tar.gz *.rpm + +dist: clean + DIR=qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec` && FILENAME=$$DIR.tar.gz && tar cvzf "$$FILENAME" --exclude "$$FILENAME" --exclude .git --exclude .gitignore -X .gitignore --transform="s|^|$$DIR/|" --show-transformed * + +rpm: dist + T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ta qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/RPMS/*/* "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" + +srpm: dist + T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ts qubes-network-server-`awk '/^Version:/ {print $$2}' qubes-network-server.spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" + +install: + install -Dm 755 src/usr/bin/qvm-static-ip -t $(DESTDIR)/$(BINDIR)/ + install -Dm 644 src/usr/lib64/python2.7/site-packages/qubes/modules/*.py -t $(DESTDIR)/$(LIBDIR)/python2.7/site-packages/qubes/modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cc7c68 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +#Qubes network server + +This software lets you turn your Qubes OS machine into a network server. + +##Enhanced networking model + +The traditional Qubes OS networking model contemplates a client-only +use case. User VMs (AppVMs or StandaloneVMs) are attached to ProxyVMs, +which give the user control over outbound connections taking place from +user VMs. ProxyVMs in turn attach to NetVMs, which provide outbound +connectivity for ProxyVMs and other user VMs alike. + +![Standard Qubes OS network model](doc/Standard Qubes OS network model.png?raw=true "Standard Qubes OS network model") + +No provision is made for running a server in a virtualized environment, +such that the server's ports are accessible by (a) other VMs (b) machines +beyond the perimeter of the NetVM. To the extent that such a thing is +possible, it is only possible by painstakingly maintaining firewall rules +for multiple VMs, which need to carefully override the existing firewall +rules, and require careful thought not to open the system to unexpected +attack vectors. The Qubes OS user interface provides no help either. + +Qubes network server changes all that. + +![Qubes network server model](doc/Qubes network server model.png?raw=true "Qubes network server model") + +With the Qubes network server software, it becomes possible to make +network servers in user VMs available to other machines, be them +peer VMs in the same Qubes OS system or machines connected to +a physical link shared by a NetVM. You get actual, full, GUI control +over network traffic, both exiting the VM and entering the VM, with +exactly the same Qubes OS user experience you are used to. + +This is all, of course, opt-in, so the standard Qubes OS network security +model remains in effect until you decide to share network servers. + +##Usage + +Once installed (see below), usage of the software is straightforward. + +To illustrate, we'll proceed with an example VM `httpserver` which +is meant to be a standalone VM that contains files, being served by +a running HTTP server (port 80) within it. This VM is attached to +a ProxyVM `server-proxy`, which in turn is connected to a NetVM +`sys-net`, with IP address `192.168.1.4` on a local network +`192.168.1.0/24`. Our goal will be to make `httpserver` accessible +to your laptop on the same physical network, which we'll assume has +IP address `192.168.1.8`. + +###Assign a static address to `httpserver` + +First step is to assign an address — let's make it `192.168.1.6` — +to `httpserver`: + +``` +qvm-static-ip -s httpserver static_ip 192.168.1.6 +``` + +###Restart `httpserver` + +Due to limitations in this release of the code, you must power off +the `httpserver` VM and then power it back on. + +###Set firewall rules on `httpserver` + +Launch the Qubes Manager preferences window for the `httpserver` VM. +Go to the *Firewall rules* tab and select *Deny network access +except...* from the top area. *Allow ICMP traffic* but deny +*DNS queries*. + +Finally, add a new network rule (use the plus button). On the +*Address* box, you're going to write `from-192.168.1.8`. Select +the *TCP* protocol, and type `80` on the *Service* box. Click OK. + +Note the trick here — any address whose text begins with +`from-` gets transformed into an incoming traffic rule, as opposed +to the standard rules that control only outbound traffic. + +Back on the main dialog, click *OK*. + +###That's it! + +You'll be able to ping, from your laptop, the address `192.168.1.6`. +You will also be able to point your browser at it, and it will +render the served pages from the HTTP server running directly on +`httpserver`. + +Save from ICMP, no other port or protocol will be allowed for +inbound connections. + +You'll also note that `httpserver` has received no permission to +engage in any sort of outbound network traffic. + +##Disabling network server + +Two-step process. Step one: + +``` +qvm-static-ip -s httpserver static_ip none +``` + +Step two: power the VM off, then start it back up. + +##Installation + +Installation is extremely easy: + +* Prepare an RPM with the `make rpm` command on the local + directory of your clone. +* Copy the prepared RPM to the dom0 of your Qubes OS + machine. +* Install the RPM with `rpm -ivh`. + +Qubes OS does not provide any facility to copy files from +a VM to the dom0. To work around this, you can use `qvm-run`: + +``` +qvm-run --pass-io vmwiththerpm 'cat /home/user/path/to/qubes-network-server*rpm' > qns.rpm +``` + +This lets you fetch the RPM file to the dom0, and save it as `qns.rpm`. diff --git a/doc/Qubes network server model.dia b/doc/Qubes network server model.dia new file mode 100644 index 0000000000000000000000000000000000000000..973b4720f65eb84cbb3474cf16010e97f6fcbd9f GIT binary patch literal 2141 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 G2K+z$0A6nZ literal 0 HcmV?d00001 diff --git a/doc/Standard Qubes OS network model.dia b/doc/Standard Qubes OS network model.dia new file mode 100644 index 0000000000000000000000000000000000000000..08e9639e8d8054aa77b1ceed8704481e313187b8 GIT binary patch literal 2360 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 literal 0 HcmV?d00001 diff --git a/doc/Standard Qubes OS network model.png b/doc/Standard Qubes OS network model.png new file mode 100644 index 0000000000000000000000000000000000000000..1f041f27ea7ba20b4186e267cedd2986cdb6a325 GIT binary patch literal 10226 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 literal 0 HcmV?d00001 diff --git a/qubes-network-server.spec b/qubes-network-server.spec new file mode 100644 index 0000000..6d4b2b8 --- /dev/null +++ b/qubes-network-server.spec @@ -0,0 +1,36 @@ +%define debug_package %{nil} + +Name: qubes-network-server +Version: 0.0.1 +Release: 1%{?dist} +Summary: Turn your Qubes OS into a network server + +License: GPLv3+ +URL: https://github.com/Rudd-O/qubes-network-server +Source0: Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz + +BuildRequires: go + +%description +This package lets you turn your Qubes OS into a network server. + +%prep +%setup -q + +%build +# variables must be kept in sync with install +make DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} LIBDIR=%{_libdir} + +%install +rm -rf $RPM_BUILD_ROOT +# variables must be kept in sync with build +make install DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} LIBDIR=%{_libdir} + +%files +%attr(0755, root, root) %{_bindir}/qvm-static-ip +%attr(0644, root, root) %{_libdir}/python2.7/site-packages/qubes/modules/*.py* +%doc README.md + +%changelog +* Tue Oct 11 2016 Manuel Amador (Rudd-O) +- Initial release diff --git a/src/usr/bin/qvm-static-ip b/src/usr/bin/qvm-static-ip new file mode 100644 index 0000000..df00de1 --- /dev/null +++ b/src/usr/bin/qvm-static-ip @@ -0,0 +1,170 @@ +#!/usr/bin/python2 +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesVmLabels +from qubes.qubes import QubesHost +from qubes.qubes import system_path +from optparse import OptionParser +import subprocess +import os +import sys +import re +from qubes.qubes import vmm + + +def do_list(vm): + label_width = 19 + fmt="{{0:<{0}}}: {{1}}".format(label_width) + + print fmt.format ("name", vm.name) + if hasattr(vm, 'static_ip'): + print fmt.format("static_ip", str(vm.static_ip) if vm.static_ip else "unset") + + +def do_get(vms, vm, prop): + if not hasattr(vm, prop): + print >>sys.stderr, "VM '{}' has no attribute '{}'".format(vm.name, + prop) + return + if getattr(vm, prop, None) is None: + # not set or set to None + return + else: + print str(getattr(vm, prop)) + + +def set_static_ip(vms, vm, args): + if len (args) != 1: + print >> sys.stderr, "Missing value ('static_ip')!" + return False + + arg = args[0] + if not arg or arg == "none" or arg == "None" or arg == "unset": + arg = None + # TODO(ruddo): validate the argument! + + setattr(vm, "static_ip", arg) + return True + +properties = { + "static_ip": set_static_ip, +} + + +def do_set(vms, vm, property, args): + if property not in properties.keys(): + print >> sys.stderr, "ERROR: Wrong property name: '{0}'".format(property) + return False + + if not hasattr(vm, property): + print >> sys.stderr, "ERROR: Property '{0}' not available for this VM".format(property) + return False + + try: + return properties[property](vms, vm, args) + except Exception as err: + print >> sys.stderr, "ERROR: %s" % str(err) + return False + + +def main(): + usage = "usage: %prog -l [options] \n"\ + "usage: %prog -g [options] \n"\ + "usage: %prog -s [options] [...]\n"\ + "List/set networking-related per-VM properties." + + parser = OptionParser (usage) + parser.add_option("-l", "--list", action="store_true", dest="do_list", + default=False) + parser.add_option("-s", "--set", action="store_true", dest="do_set", + default=False) + parser.add_option ("-g", "--get", action="store_true", dest="do_get", + default=False) + parser.add_option("--force-root", action="store_true", dest="force_root", + default=False, + help="Force to run, even with root privileges") + parser.add_option ("--offline-mode", dest="offline_mode", + action="store_true", default=False, + help="Offline mode") + + (options, args) = parser.parse_args () + if (len (args) < 1): + parser.error ("You must provide at least the vmname!") + + vmname = args[0] + + if hasattr(os, "geteuid") and os.geteuid() == 0: + if not options.force_root: + print >> sys.stderr, "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems." + print >> sys.stderr, "Retry as unprivileged user." + print >> sys.stderr, "... or use --force-root to continue anyway." + exit(1) + + if options.do_list + options.do_set + options.do_get > 1: + print >> sys.stderr, "You can provide at most one of -l, -g and -s at " \ + "the same time!" + exit(1) + + if options.offline_mode: + vmm.offline_mode = True + + if options.do_set: + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + else: + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None or vm.qid not in qvm_collection: + print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + if options.do_set: + if len (args) < 2: + print >> sys.stderr, "You must specify the property you wish to set..." + print >> sys.stderr, "Available properties:" + for p in properties.keys(): + if hasattr(vm, p): + print >> sys.stderr, "--> '{0}'".format(p) + exit (1) + + property = args[1] + if do_set(qvm_collection, vm, property, args[2:]): + qvm_collection.save() + qvm_collection.unlock_db() + else: + qvm_collection.unlock_db() + exit(1) + + elif options.do_get or len(args) == 2: + do_get(qvm_collection, vm, args[1]) + else: + # do_list + do_list(vm) + +main() diff --git a/src/usr/lib64/python2.7/site-packages/qubes/modules/001FortressQubesVm.py b/src/usr/lib64/python2.7/site-packages/qubes/modules/001FortressQubesVm.py new file mode 100644 index 0000000..1562e22 --- /dev/null +++ b/src/usr/lib64/python2.7/site-packages/qubes/modules/001FortressQubesVm.py @@ -0,0 +1,401 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2013 Marek Marczykowski +# +# 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 datetime +import base64 +import hashlib +import fcntl +import logging +import lxml.etree +import os +import pipes +import re +import shutil +import subprocess +import sys +import textwrap +import time +import uuid +import xml.parsers.expat +import signal +from qubes import qmemman +from qubes import qmemman_algo +import libvirt + +from qubes.qubes import QubesException +from qubes.qubes import QubesVm as OriginalQubesVm +from qubes.qubes import register_qubes_vm_class +from qubes.qubes import dry_run + + +fw_encap = textwrap.dedent(""" + mkdir -p /run/fortress/firewall + f=$(mktemp --tmpdir=/run/fortress/firewall) + cat > "$f" + chmod +x "$f" + bash -e "$f" + ret=$? + rm -f "$f" + exit $ret +""") + + +def locked(programtext): + if not programtext.strip(): + return programtext + return "(\nflock 200\n" + programtext + "\n) 200>/var/run/xen-hotplug/vif-lock\n" + + +def logger(programtext): + if not programtext.strip(): + return programtext + return "exec 1> >(logger -s -t fortress) 2>&1\n" + programtext + + +class QubesVm(OriginalQubesVm): + + def get_attrs_config(self): + attrs = OriginalQubesVm.get_attrs_config(self) + attrs["static_ip"] = { + "attr": "static_ip", + "default": None, + "order": 70, + "save": lambda: str(getattr(self, "static_ip")) if getattr(self, "static_ip") is not None else 'none' + } + return attrs + + @property + def ip(self): + if self.netvm is not None: + if getattr(self, "static_ip") is not None: + return getattr(self, "static_ip") + return self.netvm.get_ip_for_vm(self.qid) + else: + return None + + @property + def netmask(self): + if self.netvm is not None: + if getattr(self, "static_ip") is not None: + # Netmasks for VMs that have a static IP are always host-only. + return "255.255.255.255" + return self.netvm.netmask + else: + return None + + def start(self, verbose = False, preparing_dvm = False, start_guid = True, + notify_function = None, mem_required = None): + if dry_run: + return + xid = OriginalQubesVm.start(self, verbose, preparing_dvm, start_guid, notify_function, mem_required) + if not preparing_dvm: + self.adjust_proxy_arp(verbose=verbose, notify_function=notify_function) + self.adjust_own_firewall_rules() + return xid + + def unpause(self): + self.log.debug('unpause()') + if dry_run: + return + + if not self.is_paused(): + raise QubesException ("VM not paused!") + + self.libvirt_domain.resume() + self.adjust_proxy_arp() + self.adjust_own_firewall_rules() + + def attach_network(self, verbose = False, wait = True, netvm = None): + self.log.debug('attach_network(netvm={!r})'.format(netvm)) + if dry_run: + return + ret = OriginalQubesVm.attach_network(self, verbose, wait, netvm) + self.adjust_proxy_arp(verbose) + return ret + + def adjust_proxy_arp(self, verbose = False, notify_function=None): + + def collect_downstream_vms(vm, vif): + if not hasattr(vm, "connected_vms"): + return list() + vms_below_me = list(vm.connected_vms.values()) + vms_below_me = [(vm, vif if vif else vm.vif) for vm in vms_below_me] + for v, vif in vms_below_me: + vms_below_me.extend(collect_downstream_vms(v, vif)) + return vms_below_me + + def addroute(ip, dev, netmask): + # This function adds routes and proxy ARP entries for the IP pointed at the + # device that the VM (IP) is behind. + dev = dev.replace("+", "0") + return "\n".join([ + "if ! ip route | grep -qF %s\\ dev\\ %s ; then" % (pipes.quote(ip), pipes.quote(dev)), + "ip route replace %s/%s dev %s metric 20001" % (pipes.quote(ip), pipes.quote(netmask), pipes.quote(dev)), + "fi", + "echo 1 > /proc/sys/net/ipv4/conf/%s/forwarding" % (pipes.quote(dev),), + "echo 1 > /proc/sys/net/ipv4/conf/%s/proxy_arp" % (pipes.quote(dev),), + "for dev in `ip link | awk -F ':' '/^[0-9]+: (eth|en|wl)/ { print $2 }'`", + "do", + " ip neigh add proxy %s dev $dev" % (pipes.quote(ip),), + "done", + ]) + + class addfwrule(object): + rules = None + addrule = textwrap.dedent(""" + declare -A savedrules + addrule() { + local table="$1" + local chain="$2" + local rule="$3" + local before="$4" + + if [ "${savedrules[$table]}" == "" ] ; then + savedrules["$table"]=$(iptables-save -t "$table") + fi + + if echo "${savedrules[$table]}" | grep -q :"${chain}" ; then + true + else + savedrules["$table"]=$( + echo "${savedrules[$table]}" | while read x + do + echo "$x" + if [ "$x" == '*'"$table" ] + then + echo "${table}: new chain ${chain}" >&2 + echo ":${chain} - [0:0]" + fi + done + ) + fi + + if [ "x$before" == "x" ] ; then + before=COMMIT + elif [ "x$before" == "xbeginning" ] ; then + before=beginning + else + before="-A $chain $before" + fi + + if [ "$before" != "beginning" ] && echo "${savedrules[$table]}" | grep -qF -- "-A $chain $rule" ; then + return + fi + + local echoed=false + savedrules["$table"]=$( + echo "${savedrules[$table]}" | while read x + do + if [ "beginning" == "$before" -a "$echoed" == "false" ] && echo "$x" | grep -q '^-A ' + then + echo "${table}: adding rule -A ${chain} ${rule} to the beginning" >&2 + echo "-A $chain $rule" + echoed=true + elif [ "$x" == "$before" ] + then + echo "${table}: adding rule -A ${chain} ${rule} before ${before}" >&2 + echo "-A $chain $rule" + fi + if [ "beginning" == "$before" -a "$x" == "-A $chain $rule" ] + then + true + else + echo "$x" + fi + done + ) + } + flushrules() { + local table="$1" + local chain="$2" + + if [ "${savedrules[$table]}" == "" ] ; then + savedrules["$table"]=$(iptables-save -t "$table") + fi + + savedrules["$table"]=$( + echo "${savedrules[$table]}" | while read x + do + if echo "$x" | grep -q "^-A $chain " ; then + echo "${table}: flushing rule $x" >&2 + else + echo "$x" + fi + done + ) + } + addfwrules() { + # This function creates the FORTRESS-ALLOW-FORWARD filter chain + # and adds rules permitting forwarding of traffic + # sent by the VM and destined to the VM. + local ipnetmask="$1" + addrule filter FORWARD "-j FORTRESS-ALLOW-FORWARD" "-i vif+ -o vif+ -j DROP" + addrule filter FORTRESS-ALLOW-FORWARD "-s $ipnetmask -j ACCEPT" + addrule filter FORTRESS-ALLOW-FORWARD "-d $ipnetmask -j ACCEPT" + } + addprrules() { + # This function creates the FORTRESS-SKIP-MASQ nat chain + # and the FORTRESS-ANTISPOOF raw chain + # and adds rules defeating masquerading and anti-spoofing + # for the IP (machine) so long as it comes from / goes to + # the VIF that the machine is behind. + local ipnetmask="$1" + local vif="$2" + addrule nat POSTROUTING "-j FORTRESS-SKIP-MASQ" "-j MASQUERADE" + addrule nat FORTRESS-SKIP-MASQ "-s $ipnetmask -j ACCEPT" + addrule nat FORTRESS-SKIP-MASQ "-d $ipnetmask -j ACCEPT" + addrule raw PREROUTING "-j FORTRESS-ANTISPOOF" beginning + addrule raw FORTRESS-ANTISPOOF "-s $ipnetmask -j ACCEPT" + } + commitrules() { + for table in "${!savedrules[@]}" ; do + echo "${savedrules[$table]}" | iptables-restore -T "$table" + done + } + flushrules filter FORTRESS-ALLOW-FORWARD + flushrules nat FORTRESS-SKIP-MASQ + flushrules raw FORTRESS-ANTISPOOF + """) + + def _add(self, ip, dev, netmask, typ): + netmask = sum([bin(int(x)).count('1') for x in netmask.split('.')]) + dev = dev.replace("+", "0") + text = self.addrule + if typ == "forward": + text += "addfwrules %s/%s\n" % (pipes.quote(ip), netmask) + elif typ == "postrouting": + text += "addprrules %s/%s %s\n" % (pipes.quote(ip), netmask, pipes.quote(dev)) + self.addrule = "" + if not self.rules: + self.rules = [] + self.rules.append(text) + + def addfw(self, ip, dev, netmask): + return self._add(ip, dev, netmask, "forward") + + def addpr(self, ip, dev, netmask): + return self._add(ip, dev, netmask, "postrouting") + + def commit(self): + if not self.rules: + return self.addrule + "\ncommitrules\n" + return "\n".join(self.rules) + "\ncommitrules\n" + + programs = [] + staticipvms = [] + ruler = addfwrule() + + # For every VM downstream of mine. + for vm, vif in collect_downstream_vms(self, None): + # If the VM is running, and it has an associated VIF + # and it has a static IP: + if vm.static_ip and vif and vm.is_running(): + staticipvms.append(vm.name) + # Add ip neighs of and routes to the VM. + # pointed at the VIF that the VM is behind. + programs.append(addroute(vm.ip, vif, vm.netmask)) + # Add prerouting and postrouting rules for the VM + # that defeat masquerading and anti-spoofing. + ruler.addpr(vm.ip, vif, vm.netmask) + # If I am a NetVM, then, additionally. + if self.type == "NetVM": + # Add filter rules for the VM + # that allow it to communicate with other VMs. + ruler.addfw(vm.ip, vif, vm.netmask) + if ruler.commit(): + programs.append(ruler.commit()) + + if not programs: + pass + elif not self.is_running() or self.is_paused(): + msg = "Not running routing programs on %s (VM is paused or off)" % (self.name,) + if notify_function: + notify_function("info", msg) + elif verbose: + print >> sys.stderr, "-->", msg + else: + programs = logger(locked("\n".join(programs))) + if not staticipvms: + msg = "Enabling preliminary routing configuration on %s" % (self.name,) + else: + msg = "Enabling routing of %s on %s" % (", ".join(staticipvms), self.name) + if notify_function: + notify_function("info", msg) + elif verbose: + print >> sys.stderr, "-->", msg + # for x in programs.splitlines(False): + # print >> sys.stderr, "---->", x + p = self.run(fw_encap, user="root", gui=False, wait=True, passio_popen=True, autostart=False) + p.stdin.write(programs) + p.stdin.close() + p.stdout.read() + retcode = p.wait() + if retcode: + msg = "Routing commands on %s failed with return code %s" % (self.name, retcode) + if notify_function: + notify_function("error", msg) + elif verbose: + print >> sys.stderr, "-->", msg + + if self.netvm: + self.netvm.adjust_proxy_arp( + verbose=verbose, + notify_function=notify_function + ) + + def adjust_own_firewall_rules(self, ruleset_script=None): + ruleset_script_path = os.path.join( + os.path.dirname(self.firewall_conf), + "firewall.conf.sh" + ) + f = open(ruleset_script_path, "a+b") + fcntl.flock(f.fileno(), fcntl.LOCK_EX) + try: + if ruleset_script: + f.seek(0) + f.truncate(0) + f.write(ruleset_script) + f.flush() + else: + f.seek(0) + ruleset_script = f.read() + + if ruleset_script: + try: + ruleset_script = logger(locked(ruleset_script)) + p = self.run(fw_encap, user="root", gui=False, wait=True, passio_popen=True, autostart=False) + p.stdin.write(ruleset_script) + p.stdin.close() + p.stdout.read() + retcode = p.wait() + f.seek(0) + f.truncate(0) + f.flush() + except QubesException, e: + pass + + finally: + f.close() + +register_qubes_vm_class(QubesVm) diff --git a/src/usr/lib64/python2.7/site-packages/qubes/modules/006FortressQubesNetVm.py b/src/usr/lib64/python2.7/site-packages/qubes/modules/006FortressQubesNetVm.py new file mode 100644 index 0000000..e69443f --- /dev/null +++ b/src/usr/lib64/python2.7/site-packages/qubes/modules/006FortressQubesNetVm.py @@ -0,0 +1,46 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2013 Marek Marczykowski +# +# 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 sys +import libvirt + +from qubes.qubes import QubesNetVm as OriginalQubesNetVm +from qubes.qubes import register_qubes_vm_class,vmm,dry_run +from qubes.qubes import defaults,system_path,vm_files +from qubes.qubes import QubesVmCollection,QubesException + + +class QubesNetVm(OriginalQubesNetVm): + + @property + def netmask(self): + if getattr(self, "static_ip"): + return "255.255.255.255" + return self.__netmask + + @property + def network(self): + return self.__network + + +register_qubes_vm_class(QubesNetVm) diff --git a/src/usr/lib64/python2.7/site-packages/qubes/modules/007FortressQubesProxyVm.py b/src/usr/lib64/python2.7/site-packages/qubes/modules/007FortressQubesProxyVm.py new file mode 100644 index 0000000..1e541b5 --- /dev/null +++ b/src/usr/lib64/python2.7/site-packages/qubes/modules/007FortressQubesProxyVm.py @@ -0,0 +1,183 @@ +#!/usr/bin/python2 +# -*- coding: utf-8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2013 Marek Marczykowski +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +from datetime import datetime + +import sys +import libvirt +import pipes + +from qubes.qubes import QubesProxyVm as OriginalQubesProxyVm +from qubes.qubes import register_qubes_vm_class,vmm,dry_run +from qubes.qubes import defaults,system_path,vm_files +from qubes.qubes import QubesVmCollection,QubesException + + +yum_proxy_ip = '10.137.255.254' +yum_proxy_port = '8082' + + +class QubesProxyVm(OriginalQubesProxyVm): + + def write_iptables_qubesdb_entry(self): + self.qdb.rm("/qubes-iptables-domainrules/") + iptables = "# Generated by Qubes Core on {0}\n".format(datetime.now().ctime()) + iptables += "*filter\n" + iptables += ":INPUT DROP [0:0]\n" + iptables += ":FORWARD DROP [0:0]\n" + iptables += ":OUTPUT ACCEPT [0:0]\n" + iptables += ":PR-QBS-FORWARD - [0:0]\n" + + # Strict INPUT rules + iptables += "-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP\n" + iptables += "-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED " \ + "-j ACCEPT\n" + iptables += "-A INPUT -p icmp -j ACCEPT\n" + iptables += "-A INPUT -i lo -j ACCEPT\n" + iptables += "-A INPUT -j REJECT --reject-with icmp-host-prohibited\n" + + iptables += "-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED " \ + "-j ACCEPT\n" + # Allow dom0 networking + iptables += "-A FORWARD -i vif0.0 -j ACCEPT\n" + # Engage in firewalling for VMs + iptables += "-A FORWARD -j PR-QBS-FORWARD\n" + # Deny inter-VMs networking + iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n" + iptables += "COMMIT\n" + self.qdb.write("/qubes-iptables-header", iptables) + + vms = [vm for vm in self.connected_vms.values()] + vms_rulesets = [] + for vm in vms: + vm_iptables = "" + + iptables="*filter\n" + conf = vm.get_firewall_conf() + + xid = vm.get_xid() + if xid < 0: # VM not active ATM + continue + + ip = vm.ip + if ip is None: + continue + + # Anti-spoof rules are added by vif-script (vif-route-qubes), here we trust IP address + + accept_action = "ACCEPT" + reject_action = "REJECT --reject-with icmp-host-prohibited" + + if conf["allow"]: + default_action = accept_action + rules_action = reject_action + else: + default_action = reject_action + rules_action = accept_action + + for rule in conf["rules"]: + if getattr(vm, "static_ip", None) and rule["address"].startswith("from-"): + ruletext = "-s {0} -d {1}".format(rule["address"][len("from-"):], ip) + if rule["netmask"] != 32: + ruletext += "/{0}".format(rule["netmask"]) + + if rule["proto"] is not None and rule["proto"] != "any": + ruletext += " -p {0}".format(rule["proto"]) + if rule["portBegin"] is not None and rule["portBegin"] > 0: + ruletext += " --dport {0}".format(rule["portBegin"]) + if rule["portEnd"] is not None and rule["portEnd"] > rule["portBegin"]: + ruletext += ":{0}".format(rule["portEnd"]) + + ruletext += " -j {0}\n".format(rules_action) + iptables += "-A PR-QBS-FORWARD " + ruletext + vm_iptables += "-A FORTRESS-INPUT " + ruletext + continue + + iptables += "-A PR-QBS-FORWARD -s {0} -d {1}".format(ip, rule["address"]) + if rule["netmask"] != 32: + iptables += "/{0}".format(rule["netmask"]) + + if rule["proto"] is not None and rule["proto"] != "any": + iptables += " -p {0}".format(rule["proto"]) + if rule["portBegin"] is not None and rule["portBegin"] > 0: + iptables += " --dport {0}".format(rule["portBegin"]) + if rule["portEnd"] is not None and rule["portEnd"] > rule["portBegin"]: + iptables += ":{0}".format(rule["portEnd"]) + + iptables += " -j {0}\n".format(rules_action) + + if conf["allowDns"] and self.netvm is not None: + # PREROUTING does DNAT to NetVM DNSes, so we need self.netvm. + # properties + iptables += "-A PR-QBS-FORWARD -s {0} -p udp -d {1} --dport 53 -j " \ + "ACCEPT\n".format(ip,self.netvm.gateway) + iptables += "-A PR-QBS-FORWARD -s {0} -p udp -d {1} --dport 53 -j " \ + "ACCEPT\n".format(ip,self.netvm.secondary_dns) + iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \ + "ACCEPT\n".format(ip,self.netvm.gateway) + iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport 53 -j " \ + "ACCEPT\n".format(ip,self.netvm.secondary_dns) + if conf["allowIcmp"]: + iptables += "-A PR-QBS-FORWARD -s {0} -p icmp -j ACCEPT\n".format(ip) + if getattr(vm, "static_ip", None): + iptables += "-A PR-QBS-FORWARD -d {0} -p icmp -j ACCEPT\n".format(ip) + vm_iptables += "-A FORTRESS-INPUT -d {0} -p icmp -j ACCEPT\n".format(ip) + if conf["allowYumProxy"]: + iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport {2} -j ACCEPT\n".format(ip, yum_proxy_ip, yum_proxy_port) + else: + iptables += "-A PR-QBS-FORWARD -s {0} -p tcp -d {1} --dport {2} -j DROP\n".format(ip, yum_proxy_ip, yum_proxy_port) + + iptables += "-A PR-QBS-FORWARD -s {0} -j {1}\n".format(ip, default_action) + if getattr(vm, "static_ip", None): + iptables += "-A PR-QBS-FORWARD -d {0} -j {1}\n".format(ip, default_action) + vm_iptables += "-A FORTRESS-INPUT -d {0} -j {1}\n".format(ip, default_action) + vm_iptables += "COMMIT\n" + vms_rulesets.append((vm, vm_iptables)) + iptables += "COMMIT\n" + self.qdb.write("/qubes-iptables-domainrules/"+str(xid), iptables) + + # no need for ending -A PR-QBS-FORWARD -j DROP, cause default action is DROP + + self.write_netvm_domid_entry() + + self.rules_applied = None + self.qdb.write("/qubes-iptables", 'reload') + + for vm, ruleset in vms_rulesets: + shell_ruleset = "echo Adjusting firewall rules to: >&2\n" + shell_ruleset += "echo %s >&2\n" % pipes.quote(ruleset.strip()) + shell_ruleset += "data=$(iptables-save -t filter)\n" + shell_ruleset += 'if ! echo "$data" | grep -q -- "^:FORTRESS-INPUT" ; then\n' + shell_ruleset += ' data=$(echo "$data" | sed "s/^:INPUT/:FORTRESS-INPUT - [0:0]\\n\\0/")\n' + shell_ruleset += "fi\n" + shell_ruleset += 'if ! echo "$data" | grep -q -- "-A INPUT -j FORTRESS-INPUT" ; then\n' + shell_ruleset += ' data=$(echo "$data" | sed -r "s|-A INPUT -i vif. -j REJECT --reject-with icmp-host-prohibited|-A INPUT -j FORTRESS-INPUT\\n\\0|")\n' + shell_ruleset += "fi\n" + shell_ruleset += 'data=$(echo "$data" | grep -v ^COMMIT$)\n' + shell_ruleset += 'data=$(echo "$data" | grep -v -- "-A FORTRESS-INPUT")\n' + shell_ruleset += 'data="$data\n"%s\n' % pipes.quote(ruleset) + shell_ruleset += 'echo "$data" | iptables-restore -T filter\n' + vm.adjust_own_firewall_rules(shell_ruleset) + + +register_qubes_vm_class(QubesProxyVm)