Include dom0 agent in source.

This commit is contained in:
Manuel Amador (Rudd-O) 2020-04-14 03:43:34 +00:00
parent 4167afed98
commit 710b18a1ed
9 changed files with 213 additions and 223 deletions

3
.gitignore vendored
View File

@ -6,3 +6,6 @@ pkgs/
*.tar.gz
*.rpm
.*.swp
build
*.egg-info
src/*.service

View File

@ -10,11 +10,12 @@ src/qubes-routing-manager.service: src/qubes-routing-manager.service.in
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: clean dist rpm srpm install
.PHONY: clean dist rpm srpm install-template install-dom0
clean:
find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
rm -rf *.tar.gz *.rpm
cd $(ROOT_DIR) || exit $$? ; find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
cd $(ROOT_DIR) || exit $$? ; rm -rf *.tar.gz *.rpm
cd $(ROOT_DIR) || exit $$? ; rm -rf *.egg-info build
dist: clean
@which rpmspec || { echo 'rpmspec is not available. Please install the rpm-build package with the command `dnf install rpm-build` to continue, then rerun this step.' ; exit 1 ; }
@ -29,6 +30,11 @@ rpm: dist
cd $(ROOT_DIR) || exit $$? ; rpmbuild --define "_srcrpmdir ." --define "_rpmdir builddir.rpm" -ta `rpmspec -q --queryformat '%{name}-%{version}.tar.gz\n' *spec | head -1`
cd $(ROOT_DIR) ; mv -f builddir.rpm/*/* . && rm -rf builddir.rpm
install: all
install-template: all
install -Dm 755 src/qubes-routing-manager -t $(DESTDIR)/$(SBINDIR)/
install -Dm 644 src/qubes-routing-manager.service -t $(DESTDIR)/$(UNITDIR)/
install-dom0:
PYTHONDONTWRITEBYTECODE=1 python3 setup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
install: install-dom0 install-template

View File

@ -1,3 +1 @@
ifneq ($(PACKAGE_SET),dom0)
RPM_SPEC_FILES=qubes-network-server.spec
endif

139
README.md
View File

@ -102,97 +102,20 @@ Here are documents that will help you take advantage of Qubes network server:
## Installation
Installation consists of two steps.
Installation consists of two steps:
### Apply software patch to `dom0`
1. Deploy the `qubes-core-admin-addon-network-server` RPM to your `dom0`.
2. Deploy the `qubes-network-server` RPM to the TemplateVM backing your
NetVM (which must be a Fedora instance). If your NetVM is a StandaloneVM,
then you must deploy this RPM to the NetVM directly.
The first step is to apply a patch to your Qubes OS `dom0`.
The patch is distributed in the source, within the `src/` directory.
Future releases of this software will contain a better method for
deploying the `dom0` part of the software.
After that, to make it all take effect:
Copy the patch to your `dom0` somehow, then apply it:
1. Power off the TemplateVM.
2. Reboot the NetVM.
```
# Change into the Python 3.5 site-packages directory.
cd /usr/lib/python3.5/site-packages
# Back up your `net.py` file.
if ! test -f qubes/vm/mix/net.py.bak
sudo cp qubes/vm/mix/net.py qubes/vm/mix/net.py.bak
fi
# Patch the `net.py` file.
sudo patch -p1 < /home/user/qubes-network-server-dom0.patch
# Restart qubesd.
sudo systemctl restart qubesd.service
```
You can now verify that `qubesd` is running:
```
sudo systemctl status qubesd.service | cat
```
#### Restoring from backup in case of failed installation.
If `qubesd` has failed for some reason, you can restore
from the backup and restart again. This will nullify
the changes you made in this routine. To restore from
the backup, use the following commands:
```
udo cp qubes/vm/mix/net.py.bak qubes/vm/mix/net.py
sudo systemctl restart qubesd.service
```
### Install the agent in the TemplateVM
The second step is to build and install the component that goes
in the TemplateVM that your NetVM (typically `sys-net` uses).
If your NetVM is a StandaloneVM, you should modify these instructions
to install the component directly in the NetVM, as in this case
your NetVM does not instantiation from a Qubes template.
Within a DisposableVM, build said package as follows:
```
# Dependencies
dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros
# Source code
git clone https://github.com/Rudd-O/qubes-network-server
# <make sure you are in the correct branch now>
cd qubes-network-server
make rpm
```
The process will output a `qubes-network-server-*.noarch.rpm` in the
directory where it ran. Copy that RPM to your TemplateVM. Here is
a trick to do that from `dom0`, which fetchs the RPM file from the DisposableVM
and saves it within the TemplateVM, which you can then feed as an argument
to the `rpm -ivh` command:
```
qvm-run --pass-io disp8474 'cat /home/user/qubes-network-server/qubes-network-server*.noarch.rpm' | \
qvm-run --pass-io fedora-30 'cat > qns.rpm'
```
You can power off the DisposableVM now.
Now you can install this package in your TemplateVM (or NetVM, if it is a StandaloneVM):
```
# (In TemplateVM)
# rpm -ivh qns.rpm
```
Now power off both your TemplateVM and your NetVM.
Then start your NetVM back up.
You can verify that the necessary component is running by launching a terminal
in your NetVM, then typing the following:
You're done. You can verify that the necessary component is running by launching
a terminal in your NetVM, then typing the following:
```
systemctl status qubes-routing-manager.service
@ -200,11 +123,51 @@ systemctl status qubes-routing-manager.service
The routing manager should show as `enabled` and `active` in the terminal output.
### How to build the packages to install
You will first build the `qubes-core-admin-addon-network-server` RPM.
To build this package, you will need to use a `chroot` jail containing
a Fedora installation of the exact same release as your `dom0` (Fedora 25
for Qubes release 4.0, Fedora 31 for Qubes release 4.1).
Copy the source of the package to your `chroot`. Then start a shell in
your `chroot`, and type `make rpm`. You may have to install some packages
in your `chroot` -- use `dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros`
to get the minimum dependency set installed.
Once built, in the source directory you will find the RPM built for the
exact release of Qubes you need.
Alternatively, you may first create a source RPM using `make srpm` on your
regular workstation, then use `mock` to rebuild the source RPM produced
in the source directory, using a Fedora release compatible with your `dom0`.
To build the `qubes-network-server` RPM, you can use a DisposableVM running
the same Fedora release as your NetVM. Build said package as follows:
```
# Dependencies
dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros
# Source code
cd /path/to/qubes-network-server
# <make sure you are in the correct branch now>
make rpm
```
The process will output a `qubes-network-server-*.noarch.rpm` in the
directory where it ran. Fish it out and save it into the VM where you'll
install it.
You can power off the DisposableVM now.
### Upgrading to new / bug-fixing releases
Follow the same procedures as above, but when asked to install the package
Follow the same procedures as above, but when asked to install the packages
with `rpm -ivh`, change it to `rpm -Uvh` (uppercase U for upgrade).
Always restart your NetVMs between upgrades.
## Theory of operation
Qubes OS relies on layer 3 (IP) routing. VMs in its networked tree send traffic through

View File

@ -1,5 +1,3 @@
%{!?python2_sitearch: %define python2_sitearch %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
%define debug_package %{nil}
%define mybuildnumber %{?build_number}%{?!build_number:1}
@ -24,14 +22,26 @@ BuildRequires: systemd-rpm-macros
Requires: qubes-core-agent-networking >= 4.0.51-1
Requires: qubes-core-agent-networking < 4.1
Requires: python2
Requires: python2-qubesdb
Requires: python3
Requires: python3-qubesdb
%description
This package lets you turn your Qubes OS into a network server.
This package lets you turn your Qubes OS into a network server. Install this
in the TemplateVM of your NetVM. Then install the companion %{name}-dom0 package
in your dom0.
Please see README.md enclosed in the package for instructions on how to use this.
%package -n qubes-core-admin-addon-network-server
Summary: dom0 administrative extension for Qubes network server
Requires: qubes-core-dom0 >= 4.0.49-1
%description -n qubes-core-admin-addon-network-server
This package lets you turn your Qubes OS into a network server. Install this
in your dom0. Then install the companion qubes-network-server package in the
TemplateVM of your NetVM.
%prep
%setup -q
@ -52,6 +62,10 @@ echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75
%config %attr(0644, root, root) %{_unitdir}/qubes-routing-manager.service
%doc README.md TODO
%files -n qubes-core-admin-addon-network-server
%attr(0644, root, root) %{python3_sitelib}/qubesnetworkserver
%{python3_sitelib}/qubesnetworkserver-*.egg-info
%post
%systemd_post qubes-routing-manager.service
@ -61,6 +75,12 @@ echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75
%postun
%systemd_postun_with_restart qubes-routing-manager.service
%post -n qubes-core-admin-addon-network-server
%systemd_post qubesd.service
%postun -n qubes-core-admin-addon-network-server
%systemd_postun_with_restart qubesd.service
%changelog
* Mon Apr 13 2020 Manuel Amador (Rudd-O) <rudd-o@rudd-o.com>
- Update to Qubes 4.0

View File

@ -0,0 +1,98 @@
import qubes.ext
import qubes.vm.templatevm
class QubesNetworkServerExtension(qubes.ext.Extension):
def shutdown_routing_for_vm(self, netvm, appvm):
self.reload_routing_for_vm(netvm, appvm, True)
def reload_routing_for_vm(self, netvm, appvm, shutdown=False):
'''Reload the routing method for the VM.'''
if not netvm.is_running():
return
for addr_family in (4, 6):
ip = appvm.ip6 if addr_family == 6 else appvm.ip
if ip is None:
continue
# report routing method
self.setup_forwarding_for_vm(netvm, appvm, ip, remove=shutdown)
def setup_forwarding_for_vm(self, netvm, appvm, ip, remove=False):
'''
Record in Qubes DB that the passed VM may be meant to have traffic
forwarded to and from it, rather than masqueraded from it and blocked
to it.
The relevant incantation on the command line to assign the forwarding
behavior is `qvm-features <VM> routing-method forward`. If the feature
is set on the TemplateVM upon which the VM is based, then that counts
as the forwarding method for the VM as well.
The counterpart code in qubes-firewall handles setting up the NetVM
with the proper networking configuration to permit forwarding without
masquerading behavior.
If `remove` is True, then we remove the respective routing method from
the Qubes DB instead.
'''
if ip is None:
return
routing_method = appvm.features.check_with_template(
'routing-method', 'masquerade'
)
base_file = '/qubes-routing-method/{}'.format(ip)
if remove:
netvm.untrusted_qdb.rm(base_file)
elif routing_method == 'forward':
netvm.untrusted_qdb.write(base_file, 'forward')
else:
netvm.untrusted_qdb.write(base_file, 'masquerade')
@qubes.ext.handler(
'domain-feature-set:routing-method',
'domain-feature-delete:routing-method',
)
def on_routing_method_changed(
self,
vm,
ignored_feature,
**kwargs
):
# pylint: disable=no-self-use,unused-argument
if 'oldvalue' not in kwargs or kwargs.get('oldvalue') != kwargs.get('value'):
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler('domain-qdb-create')
def on_domain_qdb_create(self, vm, event, **kwargs):
''' Fills the QubesDB with firewall entries. '''
# pylint: disable=unused-argument
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler('domain-start')
def on_domain_started(self, vm, event, **kwargs):
# pylint: disable=unused-argument
try:
for downstream_vm in vm.connected_vms:
self.reload_routing_for_vm(vm, downstream_vm)
except AttributeError:
pass
@qubes.ext.handler('domain-shutdown')
def on_domain_shutdown(self, vm, event, **kwargs):
# pylint: disable=unused-argument
try:
for downstream_vm in self.connected_vms:
self.shutdown_routing_for_vm(vm, downstream_vm)
except AttributeError:
pass
if vm.netvm:
self.shutdown_routing_for_vm(vm.netvm, vm)
@qubes.ext.handler('net-domain-connect')
def on_net_domain_connect(self, vm, event):
# pylint: disable=unused-argument
if vm.netvm:
self.reload_routing_for_vm(vm.netvm, vm)

24
setup.py Normal file
View File

@ -0,0 +1,24 @@
import os
import setuptools
if __name__ == '__main__':
version = open(os.path.join(os.path.dirname(__file__), 'qubes-network-server.spec')).read().strip()
version = [v for v in version.splitlines() if v.startswith("Version:")][0]
version = version.split()[-1]
setuptools.setup(
name='qubesnetworkserver',
version=version,
author='Manuel Amador (Rudd-O)',
author_email='rudd-o@rudd-o.com',
description='Qubes network server dom0 component',
license='GPL2+',
url='https://github.com/Rudd-O/qubes-network-server',
packages=('qubesnetworkserver',),
entry_points={
'qubes.ext': [
'qubesnetworkserver = qubesnetworkserver:QubesNetworkServerExtension',
],
}
)

View File

@ -1,122 +0,0 @@
diff --git a/qubes/vm/mix/net.py b/qubes/vm/mix/net.py
index 129bc107..fac6ec52 100644
--- a/qubes/vm/mix/net.py
+++ b/qubes/vm/mix/net.py
@@ -262,6 +262,8 @@ class NetVMMixin(qubes.events.Emitter):
This will allow re-reconnecting them cleanly later.
'''
# pylint: disable=unused-argument
+ if self.netvm:
+ self.netvm.shutdown_routing_for_vm(self)
for vm in self.connected_vms:
if not vm.is_running():
continue
@@ -271,6 +273,19 @@ class NetVMMixin(qubes.events.Emitter):
# ignore errors
pass
+ @qubes.ext.handler(
+ 'domain-feature-set:routing-method',
+ 'domain-feature-delete:routing-method',
+ )
+ def on_routing_method_changed(
+ self,
+ event, feature,
+ value=None, oldvalue=None
+ ):
+ # pylint: disable=no-self-use,unused-argument
+ if self.netvm:
+ self.netvm.reload_routing_for_vm(self)
+
@qubes.events.handler('domain-start')
def on_domain_started(self, event, **kwargs):
'''Connect this domain to its downstream domains. Also reload firewall
@@ -281,6 +296,7 @@ class NetVMMixin(qubes.events.Emitter):
if self.netvm:
self.netvm.reload_firewall_for_vm(self) # pylint: disable=no-member
+ self.netvm.reload_routing_for_vm(self) # pylint: disable=no-member
for vm in self.connected_vms:
if not vm.is_running():
@@ -350,6 +366,20 @@ class NetVMMixin(qubes.events.Emitter):
return self.netvm is not None
+ def shutdown_routing_for_vm(self, vm):
+ self.reload_routing_for_vm(vm, True)
+
+ def reload_routing_for_vm(self, vm, shutdown=False):
+ '''Reload the routing method for the VM.'''
+ if not self.is_running():
+ return
+ for addr_family in (4, 6):
+ ip = vm.ip6 if addr_family == 6 else vm.ip
+ if ip is None:
+ continue
+ # report routing method
+ self.setup_non_masquerade_forwarding_for_vm(vm, ip, remove=shutdown)
+
def reload_firewall_for_vm(self, vm):
''' Reload the firewall rules for the vm '''
if not self.is_running():
@@ -370,6 +400,35 @@ class NetVMMixin(qubes.events.Emitter):
# signal its done
self.untrusted_qdb.write(base_dir[:-1], '')
+ def setup_non_masquerade_forwarding_for_vm(self, vm, ip, remove=False):
+ '''
+ Record in Qubes DB that the passed VM may be meant to have traffic
+ forwarded to and from it, rather than masqueraded from it and blocked
+ to it.
+
+ The relevant incantation on the command line to assign the forwarding
+ behavior is `qvm-features <VM> routing-method forward`. If the feature
+ is set on the TemplateVM upon which the VM is based, then that counts
+ as the forwarding method for the VM as well.
+
+ The counterpart code in qubes-firewall handles setting up the NetVM
+ with the proper networking configuration to permit forwarding without
+ masquerading behavior.
+
+ If `remove` is True, then we remove the respective routing method from
+ the Qubes DB instead.
+ '''
+ if ip is None:
+ return
+ routing_method = vm.features.check_with_template('routing-method', 'masquerade')
+ base_file = '/qubes-routing-method/{}'.format(ip)
+ if remove:
+ self.untrusted_qdb.rm(base_file)
+ elif routing_method == 'forward':
+ self.untrusted_qdb.write(base_file, 'forward')
+ else:
+ self.untrusted_qdb.write(base_file, 'masquerade')
+
def set_mapped_ip_info_for_vm(self, vm):
'''
Set configuration to possibly hide real IP from the VM.
@@ -451,6 +510,7 @@ class NetVMMixin(qubes.events.Emitter):
''' Reloads the firewall config for vm '''
# pylint: disable=unused-argument
self.reload_firewall_for_vm(vm)
+ self.reload_routing_for_vm(vm)
@qubes.events.handler('domain-qdb-create')
def on_domain_qdb_create(self, event):
@@ -461,6 +521,7 @@ class NetVMMixin(qubes.events.Emitter):
# keep in sync with on_firewall_changed
self.set_mapped_ip_info_for_vm(vm)
self.reload_firewall_for_vm(vm)
+ self.reload_routing_for_vm(vm)
@qubes.events.handler('firewall-changed', 'domain-spawn')
def on_firewall_changed(self, event, **kwargs):
@@ -469,6 +530,7 @@ class NetVMMixin(qubes.events.Emitter):
if self.is_running() and self.netvm:
self.netvm.set_mapped_ip_info_for_vm(self)
self.netvm.reload_firewall_for_vm(self) # pylint: disable=no-member
+ self.netvm.reload_routing_for_vm(self) # pylint: disable=no-member
# CORE2: swallowed get_firewall_conf, write_firewall_conf,
# get_firewall_defaults

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
'''
This program reads the /qubes-firewall/{ip}/qubes-routing-method file
@ -42,7 +42,7 @@ class AdjunctWorker(object):
def run_ipt(*args):
return subprocess.check_call([cmd, '-w'] + list(args))
out = subprocess.check_output([cmd + '-save']).splitlines()
out = subprocess.check_output([cmd + '-save', '-w'], universal_newlines=True).splitlines()
if enable:
# Create necessary prerouting chain.