Initial commit.

This commit is contained in:
Manuel Amador (Rudd-O) 2016-10-11 19:06:09 +00:00
commit 7ad6b81670
12 changed files with 981 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.pyc
*~
*.tar.gz
*.rpm

20
Makefile Normal file
View File

@ -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

121
README.md Normal file
View File

@ -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`.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

36
qubes-network-server.spec Normal file
View File

@ -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) <rudd-o@rudd-o.com>
- Initial release

170
src/usr/bin/qvm-static-ip Normal file
View File

@ -0,0 +1,170 @@
#!/usr/bin/python2
# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
from qubes.qubes import QubesVmCollection
from 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] <vm-name>\n"\
"usage: %prog -g [options] <vm-name> <property>\n"\
"usage: %prog -s [options] <vm-name> <property> [...]\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()

View File

@ -0,0 +1,401 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import 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)

View File

@ -0,0 +1,46 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import 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)

View File

@ -0,0 +1,183 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
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)