mirror of
https://github.com/Rudd-O/qubes-network-server.git
synced 2026-04-07 00:48:48 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658e6dfa08 | ||
|
|
86fa5c509c | ||
|
|
15edce34a8 | ||
|
|
3a214bdfe1 | ||
|
|
f7bfd46bdc | ||
|
|
26b0b2a357 | ||
|
|
7289f59867 | ||
|
|
2bc9d929f9 | ||
|
|
022414a4f8 | ||
|
|
ff3d6b55f2 | ||
|
|
ef4845548f | ||
|
|
7b5cae5b0e | ||
|
|
e9e65f7da1 | ||
|
|
da69c75642 | ||
|
|
3f58f6bda6 | ||
|
|
e5534a5225 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ build
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
src/*.service
|
src/*.service
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
.tox
|
||||||
|
|||||||
8
Jenkinsfile
vendored
8
Jenkinsfile
vendored
@ -2,10 +2,4 @@
|
|||||||
@Library('shared-jenkins-libraries@master') _
|
@Library('shared-jenkins-libraries@master') _
|
||||||
|
|
||||||
|
|
||||||
def test_step() {
|
genericFedoraRPMPipeline(null, null, null, null, TestStrategySkipTests())
|
||||||
return {
|
|
||||||
println "Tests disabled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
genericFedoraRPMPipeline(null, null, null, null, test_step())
|
|
||||||
|
|||||||
5
Makefile
5
Makefile
@ -11,7 +11,7 @@ src/qubes-routing-manager.service: src/qubes-routing-manager.service.in
|
|||||||
|
|
||||||
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
.PHONY: clean dist rpm srpm install-template install-dom0
|
.PHONY: clean dist rpm srpm install-template install-dom0 test
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cd $(ROOT_DIR) || exit $$? ; find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
|
cd $(ROOT_DIR) || exit $$? ; find -name '*.pyc' -o -name '*~' -print0 | xargs -0 rm -f
|
||||||
@ -42,3 +42,6 @@ install-dom0:
|
|||||||
PYTHONDONTWRITEBYTECODE=1 python3 networkserversetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
|
PYTHONDONTWRITEBYTECODE=1 python3 networkserversetup.py install $(PYTHON_PREFIX_ARG) -O0 --root $(DESTDIR)
|
||||||
|
|
||||||
install: install-dom0 install-template
|
install: install-dom0 install-template
|
||||||
|
|
||||||
|
test:
|
||||||
|
tox --current-env
|
||||||
|
|||||||
99
README.md
99
README.md
@ -8,6 +8,11 @@ of setting up your own Xen server.
|
|||||||
This release is only intended for use with Qubes OS 4.2. Older Qubes OS releases
|
This release is only intended for use with Qubes OS 4.2. Older Qubes OS releases
|
||||||
will not support it. For Qubes OS 4.1, check branch `r4.1`.
|
will not support it. For Qubes OS 4.1, check branch `r4.1`.
|
||||||
|
|
||||||
|
**Important note about upgrades**: when you upgrade your system from Qubes OS 4.1 to
|
||||||
|
Qubes OS 4.2, if you have this package installed in your template, the template will
|
||||||
|
likely **fail to update**. Please consult [our upgrade instructions](doc/distupgrade.md)
|
||||||
|
for information on how to proceed.
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
Qubes OS is a magnificent operating system. That said, there are many use cases its networking
|
Qubes OS is a magnificent operating system. That said, there are many use cases its networking
|
||||||
@ -38,15 +43,18 @@ connectivity for ProxyVMs and other user VMs alike.
|
|||||||
|
|
||||||
No provision is made for running a server in a virtualized environment,
|
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
|
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
|
beyond the perimeter of the NetVM. By default, firewall rules in NetVMs
|
||||||
possible, it is only possible by painstakingly maintaining firewall rules
|
prevent traffic from reaching any VM attached to them. Furthermore, even
|
||||||
for multiple VMs, which need to carefully override the existing firewall
|
with custom, permissive firewall rules, the IP addresses of VMs attached
|
||||||
rules, and require careful thought not to open the system to unexpected
|
to any NetVM are not visible "on the other side of the NetVM", so firewall
|
||||||
attack vectors. The Qubes OS user interface provides no help either.
|
rules can only help with something like DNAT. Finally, such custom firewalls
|
||||||
|
require careful thought not to open the system to unexpected attack vectors.
|
||||||
|
The Qubes OS user interface provides no means to set this up either.
|
||||||
|
|
||||||
### The Qubes network server networking model
|
### The Qubes network server networking model
|
||||||
|
|
||||||
Qubes network server builds on that.
|
Qubes network server builds on the Qubes security model and enhances it
|
||||||
|
to optionally permit traffic to user VMs.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -55,7 +63,8 @@ network servers in user VMs available to other machines, be them
|
|||||||
peer VMs in the same Qubes OS system or machines connected to
|
peer VMs in the same Qubes OS system or machines connected to
|
||||||
a physical link shared by a NetVM. Those network server VMs also
|
a physical link shared by a NetVM. Those network server VMs also
|
||||||
obey the Qubes OS outbound firewall rules controls, letting you run
|
obey the Qubes OS outbound firewall rules controls, letting you run
|
||||||
services with outbound connections restricted.
|
services with outbound connections restricted using the standard Qubes OS
|
||||||
|
firewall system.
|
||||||
|
|
||||||
This is all, of course, opt-in, so the standard Qubes OS network security
|
This is all, of course, opt-in, so the standard Qubes OS network security
|
||||||
model remains in effect until you decide to enable the feature on any
|
model remains in effect until you decide to enable the feature on any
|
||||||
@ -73,43 +82,55 @@ the software is straightforward.
|
|||||||
|
|
||||||
These sample instructions assume:
|
These sample instructions assume:
|
||||||
|
|
||||||
* you understand the distinction between dom0 and qubes
|
* the software is properly installed (see below),
|
||||||
* you already have an AppVM VM set up, named `testvm`,
|
* you understand the distinction between dom0 and qubes,
|
||||||
|
* you already have an AppVM VM set up, named `testvm`, and
|
||||||
* your `sys-net` VM is attached to a network with subnet `192.168.16.0/24`
|
* your `sys-net` VM is attached to a network with subnet `192.168.16.0/24`
|
||||||
— this, of course, may vary depending on your local router configuration.
|
— this, of course, may vary depending on your local router configuration.
|
||||||
|
|
||||||
*Do not proceed any further if you do not yet meet these requirements.*
|
*Do not proceed any further if you do not yet meet these requirements.*
|
||||||
|
|
||||||
First, attach the VM you want to expose to the network
|
First, **attach** the VM you want to expose to the network
|
||||||
to a NetVM that has an active network connection:
|
to a NetVM that has an active network connection:
|
||||||
|
|
||||||
`qvm-prefs -s testvm netvm sys-net`
|
`qvm-prefs -s testvm netvm sys-net`
|
||||||
|
|
||||||
Then, set an IP address on the VM:
|
Set an **IP** address on `testvm` belonging to the same LAN as `sys-net`:
|
||||||
|
|
||||||
`qvm-prefs -s testvm ip 192.168.16.25`
|
`qvm-prefs -s testvm ip 192.168.16.25`
|
||||||
|
|
||||||
(The step above requires you restart the `testvm` VM if it was running.)
|
**Restart** the `testvm` VM if it was already running.
|
||||||
|
|
||||||
Then, to enable the network server feature for your `testvm` VM, all you have
|
**Configure** routing method; to enable the network server feature for
|
||||||
to do in your AdminVM (`dom0`) is run the following command:
|
your `testvm` VM, all you have to do in your AdminVM (`dom0`) is run
|
||||||
|
the following command:
|
||||||
|
|
||||||
`qvm-features testvm routing-method forward`
|
`qvm-features testvm routing-method forward`
|
||||||
|
|
||||||
Now `testvm` is exposed to the network with address `192.168.16.25`, as well
|
Now `testvm` is exposed to the network with address `192.168.16.25`, as well
|
||||||
as to other VMs attached to `NetVM`.
|
as to other VMs attached to `NetVM`.
|
||||||
|
|
||||||
Do note that `testvm` will have the standard Qubes OS firewall rules stopping
|
Finally, adjust **input firewall rules** on `testvm` to permit traffic coming from
|
||||||
inbound traffic. To solve that issue, you can
|
machines in your LAN. `testvm` will have the standard Qubes OS firewall
|
||||||
[use the standard `rc.local` Qubes OS mechanism to alter the firewall rules](https://www.qubes-os.org/doc/firewall/#where-to-put-firewall-rules)
|
rules stopping inbound traffic. To solve that issue, you can use a sample
|
||||||
in your `testvm` AppVM.
|
rule in `testvm`:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo nft add rule qubes custom-input ip saddr 192.168.16.0/24 ct state new,established,related counter accept
|
||||||
|
```
|
||||||
|
|
||||||
|
You can make these rules persistent by [following instructions on the Qubes
|
||||||
|
OS firewall documentation page](https://www.qubes-os.org/doc/firewall/#enabling-networking-between-two-qubes).
|
||||||
|
Note that you do not need to change the `custom-forward` chain at all
|
||||||
|
on any qube -- Qubes network server manages that for you transparently
|
||||||
|
in your `NetVM`.
|
||||||
|
|
||||||
Here are documents that will help you take advantage of Qubes network server:
|
Here are documents that will help you take advantage of Qubes network server:
|
||||||
|
|
||||||
* [Setting up your first server](doc/Setting up your first server.md)
|
* [Setting up your first server](doc/Setting up your first server.md)
|
||||||
* [Setting up an SSH server](doc/Setting up an SSH server.md)
|
* [Setting up an SSH server](doc/Setting up an SSH server.md)
|
||||||
|
|
||||||
## Installation of packages
|
## Setup
|
||||||
|
|
||||||
Package installation consists of two steps (**the package creation instructions are below**):
|
Package installation consists of two steps (**the package creation instructions are below**):
|
||||||
|
|
||||||
@ -130,30 +151,38 @@ a terminal in your NetVM, then typing the following:
|
|||||||
systemctl status qubes-routing-manager.service
|
systemctl status qubes-routing-manager.service
|
||||||
```
|
```
|
||||||
|
|
||||||
The routing manager should show as `enabled` and `active` in the terminal output.
|
The routing manager should show as `enabled` and `active` in the terminal
|
||||||
|
output, with no errors. You can now follow the usage instructions above.
|
||||||
|
|
||||||
### How to build the packages to install
|
### How to build the packages to install
|
||||||
|
|
||||||
You will first build the `qubes-core-admin-addon-network-server` RPM.
|
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
|
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
|
a Fedora installation of the exact same release as your `dom0` (Fedora 37
|
||||||
for Qubes release 4.0, Fedora 32 for Qubes release 4.1).
|
for Qubes 4.2). You can do this using `toolbox` (for maximum safety
|
||||||
|
within a disposable qube):
|
||||||
|
|
||||||
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
|
dnf install -y toolbox
|
||||||
in your `chroot` -- use `dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros`
|
toolbox create -r 37
|
||||||
to get the minimum dependency set installed.
|
toolbox enter fedora-toolbox-37
|
||||||
|
# Bam! You have a shell in an isolated Fedora 37 instance now.
|
||||||
|
```
|
||||||
|
|
||||||
Once built, in the source directory you will find the RPM built for the
|
Within the toolbox, all your normal files from your home directory are
|
||||||
exact release of Qubes you need.
|
visible. Change into the directory that contains this source code,
|
||||||
|
then type `make rpm`. You may have to install some packages within your
|
||||||
|
toolbox -- use `dnf install git rpm-build make coreutils tar gawk findutils systemd systemd-rpm-macros`
|
||||||
|
to get the minimum dependency set installed. Don't worry -- nothing within
|
||||||
|
the toolbox affects
|
||||||
|
|
||||||
Alternatively, you may first create a source RPM using `make srpm` on your
|
Once built, in the source directory you will find the
|
||||||
regular workstation, then use `mock` to rebuild the source RPM produced
|
`qubes-core-admin-addon-network-server` RPM built for your dom0.
|
||||||
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
|
To build the `qubes-network-server` RPM that goes in your template, you
|
||||||
the same Fedora release as your NetVM. Build said package as follows:
|
can simply use a DisposableVM running the same Fedora release as your NetVM.
|
||||||
|
Build said package as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Dependencies
|
# Dependencies
|
||||||
@ -165,8 +194,8 @@ make rpm
|
|||||||
```
|
```
|
||||||
|
|
||||||
The process will output a `qubes-network-server-*.noarch.rpm` in the
|
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
|
directory where it ran. Fish it out and copy it into the template where
|
||||||
install it.
|
you'll install it.
|
||||||
|
|
||||||
You can power off the DisposableVM now.
|
You can power off the DisposableVM now.
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
["RELEASE": "q4.2 37 38 39"]
|
["QUBES_RELEASES": "4.2"]
|
||||||
|
|||||||
64
doc/distupgrade.md
Normal file
64
doc/distupgrade.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# How to upgrade a Qubes network server from Qubes OS 4.1 to Qubes OS 4.2
|
||||||
|
|
||||||
|
The [standard instructions to upgrade Qubes OS systems](https://www.qubes-os.org/doc/upgrade/4.2/)
|
||||||
|
will fail to work. The instructions tell you to run something to the effect of:
|
||||||
|
|
||||||
|
```
|
||||||
|
qubes-dist-upgrade --all-pre-reboot <other flags>
|
||||||
|
```
|
||||||
|
|
||||||
|
then reboot, then run:
|
||||||
|
|
||||||
|
```
|
||||||
|
qubes-dist-upgrade --all-post-reboot <other flags>
|
||||||
|
```
|
||||||
|
|
||||||
|
The pre-reboot phase will fail if run without the following precautions.
|
||||||
|
|
||||||
|
## Step by step instructions
|
||||||
|
|
||||||
|
First, build a `qubes-network-server` RPM with the instructions provided
|
||||||
|
by this package's [README.md](../README.md) file. Then, for each template
|
||||||
|
where `qubes-network-server` is installed, deposit your build of the
|
||||||
|
`qubes-network-server` RPM in a folder `/root/update` of the template,
|
||||||
|
and run the command `createrepo_c /root/update` (you may have to install
|
||||||
|
package `createrepo_c` via `dnf` to run it).
|
||||||
|
|
||||||
|
Now build a `qubes-core-admin-addon-network-server` package for your dom0,
|
||||||
|
then copy the file to your profile directory into dom0. Remember this
|
||||||
|
package has to be built *in the same Fedora release (37)* as the Qubes OS
|
||||||
|
4.2 dom0 (the `toolbox` command in a disposable qube is handy for this!).
|
||||||
|
|
||||||
|
Now open the file `/etc/dnf/dnf.conf` on every template qube where you
|
||||||
|
did the above, then add an `exclude=qubes-network-server` setting under
|
||||||
|
its `[main]` section.
|
||||||
|
|
||||||
|
Remove the currently-installed `qubes-core-admin-addon-network-server`
|
||||||
|
package from your dom0 (using `dnf remove`).
|
||||||
|
|
||||||
|
Run the pre-reboot phase.
|
||||||
|
|
||||||
|
Install the recently-built `qubes-core-admin-addon-network-server` package
|
||||||
|
into dom0 (using `dnf install` with the path to the RPM file).
|
||||||
|
|
||||||
|
Reboot.
|
||||||
|
|
||||||
|
Before running the post-reboot phase, remove the setting you added to the
|
||||||
|
`dnf.conf` file of each template you modified. Finally, add the file
|
||||||
|
`/etc/yum.repos.d/local.repo` with the following contents:
|
||||||
|
|
||||||
|
```
|
||||||
|
[local]
|
||||||
|
name=Local packages
|
||||||
|
baseurl=file:///root/update
|
||||||
|
enabled=1
|
||||||
|
gpgcheck=0
|
||||||
|
metadata_expire=15
|
||||||
|
```
|
||||||
|
|
||||||
|
Now run the post-reboot phase. The template upgrade should succeed now.
|
||||||
|
|
||||||
|
To finalize, delete folder `/root/update` and file `/etc/yum.repos.d/local.repo`
|
||||||
|
from every template that has it.
|
||||||
|
|
||||||
|
You are now updated to Qubes OS 4.2 and `qubes-network-server` is ready.
|
||||||
@ -3,7 +3,7 @@
|
|||||||
%define mybuildnumber %{?build_number}%{?!build_number:1}
|
%define mybuildnumber %{?build_number}%{?!build_number:1}
|
||||||
|
|
||||||
Name: qubes-network-server
|
Name: qubes-network-server
|
||||||
Version: 0.1.0
|
Version: 0.1.6
|
||||||
Release: %{mybuildnumber}%{?dist}
|
Release: %{mybuildnumber}%{?dist}
|
||||||
Summary: Turn your Qubes OS into a network server
|
Summary: Turn your Qubes OS into a network server
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
@ -19,8 +19,12 @@ BuildRequires: findutils
|
|||||||
BuildRequires: python3
|
BuildRequires: python3
|
||||||
BuildRequires: python3-rpm-macros
|
BuildRequires: python3-rpm-macros
|
||||||
BuildRequires: systemd-rpm-macros
|
BuildRequires: systemd-rpm-macros
|
||||||
|
BuildRequires: python3-tox-current-env
|
||||||
|
BuildRequires: python3-mypy
|
||||||
|
BuildRequires: python3-pytest
|
||||||
|
|
||||||
Requires: qubes-core-agent-networking >= 4.2
|
Requires: qubes-core-agent-networking >= 4.2
|
||||||
|
Conflicts: qubes-core-agent < 4.2
|
||||||
Requires: python3
|
Requires: python3
|
||||||
Requires: python3-qubesdb
|
Requires: python3-qubesdb
|
||||||
Requires: nftables
|
Requires: nftables
|
||||||
@ -46,6 +50,7 @@ BuildRequires: python3-setuptools
|
|||||||
|
|
||||||
Requires: python3
|
Requires: python3
|
||||||
Requires: qubes-core-dom0 >= 4.2
|
Requires: qubes-core-dom0 >= 4.2
|
||||||
|
Conflicts: qubes-core-dom0 < 4.2
|
||||||
|
|
||||||
%description -n qubes-core-admin-addon-network-server
|
%description -n qubes-core-admin-addon-network-server
|
||||||
This package lets you turn your Qubes OS into a network server. Install this
|
This package lets you turn your Qubes OS into a network server. Install this
|
||||||
@ -69,6 +74,9 @@ make install DESTDIR=$RPM_BUILD_ROOT SBINDIR=%{_sbindir} UNITDIR=%{_unitdir} PYT
|
|||||||
mkdir -p "$RPM_BUILD_ROOT"/%{_presetdir}
|
mkdir -p "$RPM_BUILD_ROOT"/%{_presetdir}
|
||||||
echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75-%{name}.preset
|
echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75-%{name}.preset
|
||||||
|
|
||||||
|
%check
|
||||||
|
tox --current-env
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%attr(0755, root, root) %{_sbindir}/qubes-routing-manager
|
%attr(0755, root, root) %{_sbindir}/qubes-routing-manager
|
||||||
%attr(0644, root, root) %{python3_sitelib}/qubesroutingmanager/*
|
%attr(0644, root, root) %{python3_sitelib}/qubesroutingmanager/*
|
||||||
@ -84,6 +92,26 @@ echo 'enable qubes-routing-manager.service' > "$RPM_BUILD_ROOT"/%{_presetdir}/75
|
|||||||
%post
|
%post
|
||||||
%systemd_post qubes-routing-manager.service
|
%systemd_post qubes-routing-manager.service
|
||||||
|
|
||||||
|
%posttrans
|
||||||
|
# Remove old unit enablement paths.
|
||||||
|
reenable=0
|
||||||
|
if [ -h %{_sysconfdir}/systemd/system/multi-user.target.wants/qubes-routing-manager.service ]
|
||||||
|
then
|
||||||
|
reenable=1
|
||||||
|
rm -f %{_sysconfdir}/systemd/system/multi-user.target.wants/qubes-routing-manager.service
|
||||||
|
fi
|
||||||
|
if [ -h %{_sysconfdir}/systemd/system/qubes-iptables.service.wants/qubes-routing-manager.service ]
|
||||||
|
then
|
||||||
|
reenable=1
|
||||||
|
rm -f %{_sysconfdir}/systemd/system/qubes-iptables.service.wants/qubes-routing-manager.service
|
||||||
|
fi
|
||||||
|
if [ $reenable = 1 ]
|
||||||
|
then
|
||||||
|
mkdir -p %{_sysconfdir}/systemd/system/qubes-network.service.wants
|
||||||
|
ln -sf %{_unitdir}/qubes-routing-manager.service %{_sysconfdir}/systemd/system/qubes-network.service.wants/qubes-routing-manager.service
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
|
||||||
%preun
|
%preun
|
||||||
%systemd_preun qubes-routing-manager.service
|
%systemd_preun qubes-routing-manager.service
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from typing import TypedDict, Any, cast, Literal
|
from typing import TypedDict, Any, cast, Literal, Union
|
||||||
|
|
||||||
|
|
||||||
ADDRESS_FAMILIES = Literal["ip"] | Literal["ip6"]
|
ADDRESS_FAMILIES = Union[Literal["ip"], Literal["ip6"]]
|
||||||
|
|
||||||
|
|
||||||
class Chain(TypedDict):
|
class Chain(TypedDict):
|
||||||
@ -65,9 +65,10 @@ ADDRESS_FAMILY_IPV6 = "ip6"
|
|||||||
ADDRESS_FAMILY_IPV4 = "ip"
|
ADDRESS_FAMILY_IPV4 = "ip"
|
||||||
TABLE_NAME = "qubes"
|
TABLE_NAME = "qubes"
|
||||||
FORWARD_CHAIN_NAME = "forward"
|
FORWARD_CHAIN_NAME = "forward"
|
||||||
|
POSTROUTING_CHAIN_NAME = "postrouting"
|
||||||
ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager"
|
ROUTING_MANAGER_CHAIN_NAME = "qubes-routing-manager"
|
||||||
|
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME = "qubes-routing-manager-postrouting"
|
||||||
NFTABLES_CMD = "nft"
|
NFTABLES_CMD = "nft"
|
||||||
ADD_RULE_AFTER_THIS_RULE = "custom-forward"
|
|
||||||
|
|
||||||
|
|
||||||
def get_table(address_family: ADDRESS_FAMILIES, table: str) -> NFTablesOutput:
|
def get_table(address_family: ADDRESS_FAMILIES, table: str) -> NFTablesOutput:
|
||||||
@ -137,15 +138,20 @@ def append_counter_at_end(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def append_rule_after(
|
def _append_or_insert_rule(
|
||||||
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
|
where: Literal["add"] | Literal["insert"],
|
||||||
|
address_family: ADDRESS_FAMILIES,
|
||||||
|
table: str,
|
||||||
|
chain: str,
|
||||||
|
handle: int,
|
||||||
|
*rest: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
[
|
[
|
||||||
NFTABLES_CMD,
|
NFTABLES_CMD,
|
||||||
"-n",
|
"-n",
|
||||||
"-j",
|
"-j",
|
||||||
"add",
|
where,
|
||||||
"rule",
|
"rule",
|
||||||
address_family,
|
address_family,
|
||||||
table,
|
table,
|
||||||
@ -158,6 +164,18 @@ def append_rule_after(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def append_rule_after(
|
||||||
|
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
|
||||||
|
) -> None:
|
||||||
|
_append_or_insert_rule("add", address_family, table, chain, handle, *rest)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_rule_before(
|
||||||
|
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int, *rest: str
|
||||||
|
) -> None:
|
||||||
|
_append_or_insert_rule("insert", address_family, table, chain, handle, *rest)
|
||||||
|
|
||||||
|
|
||||||
def delete_rule(
|
def delete_rule(
|
||||||
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int
|
address_family: ADDRESS_FAMILIES, table: str, chain: str, handle: int
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -202,109 +220,160 @@ def setup_plain_forwarding_for_address(source: str, enable: bool, family: int) -
|
|||||||
forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][
|
forward_chain = [x for x in existing_chains if x["name"] == FORWARD_CHAIN_NAME][
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
|
postrouting_chain = [
|
||||||
|
x for x in existing_chains if x["name"] == POSTROUTING_CHAIN_NAME
|
||||||
|
][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logging.warn(
|
logging.warn(
|
||||||
"No forward chain in table %s, not setting up forwarding", TABLE_NAME
|
"No forward or postrouting chains in table %s, not setting up forwarding",
|
||||||
|
TABLE_NAME,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
qubes_routing_manager_chain: None | Chain = None
|
for chain_name in [
|
||||||
try:
|
ROUTING_MANAGER_CHAIN_NAME,
|
||||||
qubes_routing_manager_chain = [
|
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME,
|
||||||
x for x in existing_chains if x["name"] == ROUTING_MANAGER_CHAIN_NAME
|
]:
|
||||||
].pop()
|
chain: None | Chain = None
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not qubes_routing_manager_chain:
|
|
||||||
logging.info(
|
|
||||||
"Adding %s chain to table %s", ROUTING_MANAGER_CHAIN_NAME, TABLE_NAME
|
|
||||||
)
|
|
||||||
add_chain(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME)
|
|
||||||
|
|
||||||
qubes_routing_manager_rule: None | Rule = None
|
|
||||||
try:
|
|
||||||
qubes_routing_manager_rule = [
|
|
||||||
x
|
|
||||||
for x in existing_rules
|
|
||||||
if x["chain"] == forward_chain["name"]
|
|
||||||
and x["family"] == af
|
|
||||||
and len(x["expr"]) == 1
|
|
||||||
and x["expr"][0].get("jump", {}).get("target") == ROUTING_MANAGER_CHAIN_NAME
|
|
||||||
].pop()
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not qubes_routing_manager_rule:
|
|
||||||
try:
|
try:
|
||||||
custom_forwarding_rule = [
|
chain = [x for x in existing_chains if x["name"] == chain_name].pop()
|
||||||
x
|
|
||||||
for x in existing_rules
|
|
||||||
if x["chain"] == forward_chain["name"]
|
|
||||||
and len(x["expr"]) == 1
|
|
||||||
and x["expr"][0].get("jump", {}).get("target")
|
|
||||||
== ADD_RULE_AFTER_THIS_RULE
|
|
||||||
][0]
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logging.warn(
|
pass
|
||||||
"No state forwarding rule in chain %s of table %s, not setting up forwarding",
|
|
||||||
forward_chain["name"],
|
if not chain:
|
||||||
|
logging.info(
|
||||||
|
"Adding %s chain to table %s and counter to chain",
|
||||||
|
chain_name,
|
||||||
TABLE_NAME,
|
TABLE_NAME,
|
||||||
)
|
)
|
||||||
logging.info(
|
add_chain(af, TABLE_NAME, chain_name)
|
||||||
"Adding rule to jump to %s to table %s after jump to %s",
|
append_counter_at_end(
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
af,
|
||||||
TABLE_NAME,
|
TABLE_NAME,
|
||||||
ADD_RULE_AFTER_THIS_RULE,
|
chain_name,
|
||||||
)
|
)
|
||||||
append_rule_after(
|
|
||||||
af,
|
def is_oifgroup_2(rule):
|
||||||
TABLE_NAME,
|
return (
|
||||||
forward_chain["name"],
|
rule["chain"] == forward_chain["name"]
|
||||||
custom_forwarding_rule["handle"],
|
and len(rule["expr"]) == 3
|
||||||
"jump",
|
and (
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
rule["expr"][0].get("match", {}).get("op") == "=="
|
||||||
)
|
and rule["expr"][0]
|
||||||
append_counter_at_end(
|
.get("match", {})
|
||||||
af,
|
.get("left", {})
|
||||||
TABLE_NAME,
|
.get("meta", {})
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
.get("key")
|
||||||
|
== "oifgroup"
|
||||||
|
and rule["expr"][0].get("match", {}).get("right") == 2
|
||||||
|
)
|
||||||
|
and (rule["expr"][-1].get("drop", "not none") is None)
|
||||||
)
|
)
|
||||||
|
|
||||||
address_rules = [
|
def is_postrouting_masquerade(rule):
|
||||||
x
|
return (
|
||||||
for x in existing_rules
|
rule["chain"] == postrouting_chain["name"]
|
||||||
if x["chain"] == ROUTING_MANAGER_CHAIN_NAME
|
and len(rule["expr"]) == 1
|
||||||
and len(x["expr"]) == 2
|
and "masquerade" in rule["expr"][0]
|
||||||
and x["expr"][0].get("match", {}).get("op", {}) == "=="
|
)
|
||||||
and x["expr"][0]["match"].get("left", {}).get("payload", {}).get("protocol", "")
|
|
||||||
== af
|
|
||||||
and x["expr"][0]["match"]["left"]["payload"].get("field", "") == "daddr"
|
|
||||||
and x["expr"][0].get("match", {}).get("right", []) == source
|
|
||||||
and "accept" in x["expr"][1]
|
|
||||||
]
|
|
||||||
|
|
||||||
if enable and not address_rules:
|
for parent_chain, child_chain_name, previous_rule_detector, insertor in [
|
||||||
logging.info(
|
(
|
||||||
"Adding accept rule on chain %s to allow traffic to %s.",
|
forward_chain,
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
ROUTING_MANAGER_CHAIN_NAME,
|
||||||
source,
|
is_oifgroup_2,
|
||||||
|
insert_rule_before,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
postrouting_chain,
|
||||||
|
ROUTING_MANAGER_POSTROUTING_CHAIN_NAME,
|
||||||
|
is_postrouting_masquerade,
|
||||||
|
insert_rule_before,
|
||||||
|
),
|
||||||
|
]:
|
||||||
|
jump_rule: None | Rule = None
|
||||||
|
try:
|
||||||
|
jump_rule = [
|
||||||
|
x
|
||||||
|
for x in existing_rules
|
||||||
|
if x["chain"] == parent_chain["name"]
|
||||||
|
and x["family"] == af
|
||||||
|
and len(x["expr"]) == 1
|
||||||
|
and x["expr"][0].get("jump", {}).get("target") == child_chain_name
|
||||||
|
].pop()
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not jump_rule:
|
||||||
|
try:
|
||||||
|
previous_rule = [
|
||||||
|
x for x in existing_rules if previous_rule_detector(x)
|
||||||
|
][0]
|
||||||
|
except IndexError:
|
||||||
|
logging.warn(
|
||||||
|
"Cannot find appropriate previous rule in chain %s of table %s, not setting up forwarding",
|
||||||
|
parent_chain["name"],
|
||||||
|
TABLE_NAME,
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
"Adding rule to jump from chain %s to chain %s in table %s",
|
||||||
|
parent_chain["name"],
|
||||||
|
child_chain_name,
|
||||||
|
TABLE_NAME,
|
||||||
|
)
|
||||||
|
insertor(
|
||||||
|
af,
|
||||||
|
TABLE_NAME,
|
||||||
|
parent_chain["name"],
|
||||||
|
previous_rule["handle"],
|
||||||
|
"jump",
|
||||||
|
child_chain_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def detect_ip_rule(rule: Rule, chain_name: str, ip: str, mode: str):
|
||||||
|
return (
|
||||||
|
rule["chain"] == chain_name
|
||||||
|
and len(rule["expr"]) == 2
|
||||||
|
and rule["expr"][0].get("match", {}).get("op", {}) == "=="
|
||||||
|
and rule["expr"][0]["match"]
|
||||||
|
.get("left", {})
|
||||||
|
.get("payload", {})
|
||||||
|
.get("protocol", "")
|
||||||
|
== af
|
||||||
|
and rule["expr"][0]["match"]["left"]["payload"].get("field", "") == mode
|
||||||
|
and rule["expr"][0].get("match", {}).get("right", []) == ip
|
||||||
|
and "accept" in rule["expr"][1]
|
||||||
)
|
)
|
||||||
append_rule_at_end(
|
|
||||||
af,
|
for chain_name, mode in [
|
||||||
TABLE_NAME,
|
(ROUTING_MANAGER_CHAIN_NAME, "daddr"),
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
(ROUTING_MANAGER_POSTROUTING_CHAIN_NAME, "saddr"),
|
||||||
af,
|
]:
|
||||||
"daddr",
|
address_rules = [
|
||||||
source,
|
x for x in existing_rules if detect_ip_rule(x, chain_name, source, mode)
|
||||||
"accept",
|
]
|
||||||
)
|
|
||||||
elif not enable and address_rules:
|
if enable and not address_rules:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Removing %s accept rules from chain %s to stop traffic to %s.",
|
"Adding accept rule on chain %s for %s.",
|
||||||
len(address_rules),
|
chain_name,
|
||||||
ROUTING_MANAGER_CHAIN_NAME,
|
source,
|
||||||
source,
|
)
|
||||||
)
|
append_rule_at_end(
|
||||||
for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])):
|
af,
|
||||||
delete_rule(af, TABLE_NAME, ROUTING_MANAGER_CHAIN_NAME, rule["handle"])
|
TABLE_NAME,
|
||||||
|
chain_name,
|
||||||
|
af,
|
||||||
|
mode,
|
||||||
|
source,
|
||||||
|
"accept",
|
||||||
|
)
|
||||||
|
elif not enable and address_rules:
|
||||||
|
logging.info(
|
||||||
|
"Removing %s accept rules from chain %s for %s.",
|
||||||
|
len(address_rules),
|
||||||
|
chain_name,
|
||||||
|
source,
|
||||||
|
)
|
||||||
|
for rule in reversed(sorted(address_rules, key=lambda r: r["handle"])):
|
||||||
|
delete_rule(af, TABLE_NAME, chain_name, rule["handle"])
|
||||||
|
|||||||
1580
qubesroutingmanager/fixtures/fully_added.json
Normal file
1580
qubesroutingmanager/fixtures/fully_added.json
Normal file
File diff suppressed because it is too large
Load Diff
1290
qubesroutingmanager/fixtures/no_routing_manager.json
Normal file
1290
qubesroutingmanager/fixtures/no_routing_manager.json
Normal file
File diff suppressed because it is too large
Load Diff
1532
qubesroutingmanager/fixtures/partially_added.json
Normal file
1532
qubesroutingmanager/fixtures/partially_added.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
import qubesdb
|
import qubesdb # type: ignore
|
||||||
|
|
||||||
from qubesroutingmanager import setup_plain_forwarding_for_address
|
from qubesroutingmanager import setup_plain_forwarding_for_address
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Configure the network to allow network server VMs
|
Description=Configure the network to allow network server VMs
|
||||||
Documentation=https://github.com/Rudd-O/qubes-network-server
|
Documentation=https://github.com/Rudd-O/qubes-network-server
|
||||||
After=qubes-iptables.service
|
After=qubes-network.service qubes-iptables.service
|
||||||
BindsTo=qubes-iptables.service
|
BindsTo=qubes-iptables.service
|
||||||
|
ConditionPathExists=/var/run/qubes-service/qubes-network
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
ExecStart=@SBINDIR@/qubes-routing-manager
|
ExecStart=@SBINDIR@/qubes-routing-manager
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=qubes-iptables.service
|
WantedBy=qubes-network.service
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user