qubes-doc/user/security-in-qubes/firewall_4.1.rst

731 lines
22 KiB
ReStructuredText
Raw Normal View History

2024-05-21 14:59:46 -04:00
============
Firewall 4.1
============
Introduction
------------
| This page explains use of the firewall in Qubes 4.1, using
``iptables``.
| From Qubes 4.2, all firewall components use ``nftables``. For details
of that usage see `here <../firewall/>`__
Understanding firewalling in Qubes
----------------------------------
Every qube in Qubes is connected to the network via a FirewallVM, which
is used to enforce network-level policies. By default there is one
default FirewallVM, but the user is free to create more, if needed.
For more information, see the following:
- https://groups.google.com/group/qubes-devel/browse_thread/thread/9e231b0e14bf9d62
- https://blog.invisiblethings.org/2011/09/28/playing-with-qubes-networking-for-fun.html
How to edit rules
-----------------
In order to edit rules for a given qube, select it in the Qube Manager
and press the “firewall” button.
.. figure:: /attachment/doc/r4.0-manager-firewall.png
:alt: r4.0-manager-firewall.png
r4.0-manager-firewall.png
If the qube is running, you can open Settings from the Qube Popup Menu.
*R4.0 note:* ICMP and DNS are no longer accessible in the GUI, but can
be changed via ``qvm-firewall`` described below. Connections to Updates
Proxy are not made over a network so can not be allowed or blocked with
firewall rules, but are controlled using the relevant policy file (see
:doc:`R4.0 Updates proxy </user/how-to-guides/how-to-install-software>` for more detail).
Note that if you specify a rule by DNS name it will be resolved to IP(s)
*at the moment of applying the rules*, and not on the fly for each new
connection. This means it will not work for servers using load
balancing, and traffic to complex web sites which draw from many servers
will be difficult to control.
Instead of using the firewall GUI, you can use the ``qvm-firewall``
command in Dom0 to edit the firewall rules by hand. This gives you
greater control than by using the GUI.
The firewall rules for each qube are saved in an XML file in that qubes
directory in dom0:
.. code:: bash
/var/lib/qubes/appvms/<vm-name>/firewall.xml
Rules are implemented on the netvm.
You can also manually create rules in the qube itself using standard
firewalling controls. See `Where to put firewall rules <#where-to-put-firewall-rules>`__. In complex cases, it might be
appropriate to load a ruleset using ``iptables-restore`` called from
``/rw/config/rc.local``. if you do this, be aware that ``rc.local`` is
called *after* the network is up, so local rules should not be relied
upon to block leaks.
Reconnecting qubes after a NetVM reboot
---------------------------------------
Normally Qubes doesnt let the user stop a NetVM if there are other
qubes running which use it as their own NetVM. But in case the NetVM
stops for whatever reason (e.g. it crashes, or the user forces its
shutdown via qvm-kill via terminal in Dom0), Qubes R4.0 will often
automatically repair the connection. If it does not, then there is an
easy way to restore the connection to the NetVM by issuing in dom0:
``qvm-prefs <vm> netvm <netvm>``
Normally qubes do not connect directly to the actual NetVM which has
networking devices, but rather to the default sys-firewall first, and in
most cases it would be the NetVM that will crash, e.g. in response to S3
sleep/restore or other issues with WiFi drivers. In that case it is only
necessary to issue the above command once, for the sys-firewall (this
assumes default VM-naming used by the default Qubes installation):
``qvm-prefs sys-firewall netvm sys-net``
Network service qubes
---------------------
Qubes does not support running any networking services (e.g. VPN, local
DNS server, IPS, …) directly in a qube that is used to run the Qubes
firewall service (usually sys-firewall) for good reasons. In particular,
if you want to ensure proper functioning of the Qubes firewall, you
should not tinker with iptables or nftables rules in such qubes.
Instead, you should deploy a network infrastructure such as
.. code:: bash
sys-net <--> sys-firewall-1 <--> network service qube <--> sys-firewall-2 <--> [client qubes]
Thereby sys-firewall-1 is only needed if you have other client qubes
connected there, or you want to manage the traffic of the local network
service qube. The sys-firewall-2 proxy ensures that:
1. Firewall changes done in the network service qube cannot render the
Qubes firewall ineffective.
2. Changes to the Qubes firewall by the Qubes maintainers cannot lead to
unwanted information leakage in combination with user rules deployed
in the network service qube.
3. A compromise of the network service qube does not compromise the
Qubes firewall.
If you adopt this model, you should be aware that all traffic will
arrive at the ``network service qube`` appearing to originate from the
IP address of ``sys-firewall-2``.
For the VPN service please also look at the `VPN documentation <https://github.com/Qubes-Community/Contents/blob/master/docs/configuration/vpn.md>`__.
Enabling networking between two qubes
-------------------------------------
Normally any networking traffic between qubes is prohibited for security
reasons. However, in special situations, you might want to selectively
allow specific qubes to establish networking connectivity between each
other. For example, this might be useful in some development work, when
you want to test networking code, or to allow file exchange between HVM
domains (which do not have Qubes tools installed) via SMB/scp/NFS
protocols.
In order to allow networking between qubes A and B follow these steps:
- Make sure both A and B are connected to the same firewall vm (by
default all VMs use the same firewall VM).
- Note the Qubes IP addresses assigned to both qubes. This can be done
using the ``qvm-ls -n`` command, or via the Qubes Manager preferences
pane for each qube.
- Start both qubes, and also open a terminal in the firewall VM
- In the firewall VMs terminal enter the following iptables rule:
.. code:: bash
sudo iptables -I FORWARD 2 -s <IP address of A> -d <IP address of B> -j ACCEPT
- In qube Bs terminal enter the following iptables rule:
.. code:: bash
sudo iptables -I INPUT -s <IP address of A> -j ACCEPT
- Now you should be able to reach B from A test it using e.g. ping
issued from A. Note however, that this doesnt allow you to reach A
from B for this you would need two more rules, with A and B
swapped.
- If everything works as expected, then you should write the above
iptables rules into firewallVMs ``qubes-firewall-user-script``
script. This script is run when the netvm starts up. You should also
write relevant rules in A and Bs ``rc.local`` script which is run
when the qube is launched. Heres an example how to update the
script:
.. code:: bash
[user@sys-firewall ~]$ sudo bash
[root@sys-firewall user]# echo "iptables -I FORWARD 2 -s 10.137.2.25 -d 10.137.2.6 -j ACCEPT" >> /rw/config/qubes-firewall-user-script
[root@sys-firewall user]# chmod +x /rw/config/qubes-firewall-user-script
- Here is an example how to update ``rc.local``:
.. code:: bash
[user@B ~]$ sudo bash
[root@B user]# echo "iptables -I INPUT -s 10.137.2.25 -j ACCEPT" >> /rw/config/rc.local
[root@B user]# chmod +x /rw/config/rc.local
Opening a single TCP port to other network-isolated qube
--------------------------------------------------------
In the case where a specific TCP port needs to be exposed from a qubes
to another one, you do not need to enable networking between them but
you can use the qubes RPC service ``qubes.ConnectTCP``.
**1. Simple port binding**
Consider the following example. ``mytcp-service`` qube has a TCP service
running on port ``444`` and ``untrusted`` qube needs to access this
service.
- In dom0, add the following to
``/etc/qubes/policy.d/30-user-networking.policy``: (it could be
``another-other-name.policy`` just remember to keep it consistent)
.. code:: bash
qubes.ConnectTCP * untrusted @default allow target=mytcp-service
- In untrusted, use the Qubes tool ``qvm-connect-tcp``:
.. code:: bash
[user@untrusted #]$ qvm-connect-tcp 444:@default:444
Note: The syntax is the same as SSH tunnel handler. The first ``444``
correspond to the localport destination of ``untrusted``,
``@default`` the remote machine and the second ``444`` to the remote
machine port.
The service of ``mytcp-service`` running on port ``444`` is now
accessible in ``untrusted`` as ``localhost:444``.
Here ``@default`` is used to hide the destination qube which is
specified in the Qubes RPC policy by ``target=mytcp-service``.
Equivalent call is to use the tool as follow:
.. code:: bash
[user@untrusted #]$ qvm-connect-tcp ::444
which means to use default local port of ``unstrusted`` as the same of
the remote port and unspecified destination qube is ``@default`` by
default in ``qrexec`` call.
**2. Binding remote port on another local port**
Consider now the case where someone prefers to specify the destination
qube and use another port in untrusted, for example ``10044``. Instead
of previous case, add
.. code:: bash
qubes.ConnectTCP * untrusted mytcp-service allow
in ``/etc/qubes/policy.d/30-user-networking.policy`` and in untrusted,
use the tool as follow:
.. code:: bash
[user@untrusted #]$ qvm-connect-tcp 10444:mytcp-service:444
The service of ``mytcp-service`` running on port ``444`` is now
accessible in ``untrusted`` as ``localhost:10444``.
**3. Binding to different qubes using RPC policies**
One can go further than the previous examples by redirecting different
ports to different qubes. For example, let assume that another qube
``mytcp-service-bis`` with a TCP service is running on port ``445``. If
someone wants ``untrusted`` to be able to reach this service but port
``445`` is reserved to ``mytcp-service-bis`` then, in dom0, add the
following to ``/etc/qubes/policy.d/30-user-networking.policy``:
.. code:: bash
qubes.ConnectTCP +445 untrusted @default allow target=mytcp-service-bis
In that case, calling ``qvm-connect-tcp`` like previous examples, will
still bind TCP port ``444`` of ``mytcp-service`` to ``untrusted`` but
now, calling it with port ``445``
.. code:: bash
[user@untrusted #]$ qvm-connect-tcp ::445
will restrict the binding to only the corresponding TCP port of
``mytcp-service-bis``.
**4. Permanent port binding**
For creating a permanent port bind between two qubes, ``systemd`` can be
used. We use the case of the first example. In ``/rw/config`` (or any
place you find suitable) of qube ``untrusted``, create
``my-tcp-service.socket`` with content:
.. code:: bash
[Unit]
Description=my-tcp-service
[Socket]
ListenStream=127.0.0.1:444
Accept=true
[Install]
WantedBy=sockets.target
and ``my-tcp-service@.service`` with content:
.. code:: bash
[Unit]
Description=my-tcp-service
[Service]
ExecStart=qrexec-client-vm '' qubes.ConnectTCP+444
StandardInput=socket
StandardOutput=inherit
In ``/rw/config/rc.local``, append the lines:
.. code:: bash
cp -r /rw/config/my-tcp-service.socket /rw/config/my-tcp-service@.service /lib/systemd/system/
systemctl daemon-reload
systemctl start my-tcp-service.socket
When the qube ``unstrusted`` has started (after a first reboot), you can
directly access the service of ``mytcp-service`` running on port ``444``
as ``localhost:444``.
Port forwarding to a qube from the outside world
------------------------------------------------
In order to allow a service present in a qube to be exposed to the
outside world in the default setup (where the qube has sys-firewall as
network VM, which in turn has sys-net as network VM) the following needs
to be done:
- In the sys-net VM:
- Route packets from the outside world to the sys-firewall VM
- Allow packets through the sys-net VM firewall
- In the sys-firewall VM:
- Route packets from the sys-net VM to the VM
- Allow packets through the sys-firewall VM firewall
- In the qube:
- Allow packets through the qube firewall to reach the service
As an example we can take the use case of a web server listening on port
443 that we want to expose on our physical interface eth0, but only to
our local network 192.168.x.0/24.
Note: To have all interfaces available and configured, make sure the
3 qubes are up and running
Note: `Issue #4028 <https://github.com/QubesOS/qubes-issues/issues/4028>`__
discusses adding a command to automate exposing the port.
**1. Identify the IP addresses you will need to use for sys-net, sys-firewall and the destination qube.**
You can get this information from the Settings Window for the qube, or
by running this command in each qube: ``ifconfig | grep -i cast`` Note
the IP addresses you will need. > Note: The vifx.0 interface is the one
used by qubes connected to this netvm so it is *not* an outside world
interface.
**2. Route packets from the outside world to the FirewallVM**
For the following example, we assume that the physical interface eth0 in
sys-net has the IP address 192.168.x.y and that the IP address of
sys-firewall is 10.137.1.z.
In the sys-net VMs Terminal, code a natting firewall rule to route
traffic on the outside interface for the service to the sys-firewall VM
.. code:: bash
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -d 192.168.x.y -j DNAT --to-destination 10.137.1.z
Code the appropriate new filtering firewall rule to allow new
connections for the service
.. code:: bash
iptables -I FORWARD 2 -i eth0 -d 10.137.1.z -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
If you want to expose the service on multiple interfaces, repeat the
steps described in part 1 for each interface. In Qubes R4, at the
moment
(`QubesOS/qubes-issues#3644 <https://github.com/QubesOS/qubes-issues/issues/3644>`__),
nftables is also used which imply that additional rules need to be
set in a ``qubes-firewall`` nft table with a forward chain.
``nft add rule ip qubes-firewall forward meta iifname eth0 ip daddr 10.137.1.z tcp dport 443 ct state new counter accept``
Verify you are cutting through the sys-net VM firewall by looking at its
counters (column 2)
.. code:: bash
iptables -t nat -L -v -n
iptables -L -v -n
Note: On Qubes R4, you can also check the nft counters
.. code:: bash
nft list table ip qubes-firewall
Send a test packet by trying to connect to the service from an external
device
.. code:: bash
telnet 192.168.x.y 443
Once you have confirmed that the counters increase, store these command
in ``/rw/config/rc.local`` so they get set on sys-net start-up
.. code:: bash
sudo nano /rw/config/rc.local
.. code:: bash
#!/bin/sh
####################
# My service routing
# Create a new firewall natting chain for my service
if iptables -w -t nat -N MY-HTTPS; then
# Add a natting rule if it did not exist (to avoid clutter if script executed multiple times)
iptables -w -t nat -A MY-HTTPS -j DNAT --to-destination 10.137.1.z
fi
# If no prerouting rule exist for my service
if ! iptables -w -t nat -n -L PREROUTING | grep --quiet MY-HTTPS; then
# add a natting rule for the traffic (same reason)
iptables -w -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -d 192.168.x.y -j MY-HTTPS
fi
######################
# My service filtering
# Create a new firewall filtering chain for my service
if iptables -w -N MY-HTTPS; then
# Add a filtering rule if it did not exist (to avoid clutter if script executed multiple times)
iptables -w -A MY-HTTPS -s 192.168.x.0/24 -j ACCEPT
fi
# If no forward rule exist for my service
if ! iptables -w -n -L FORWARD | grep --quiet MY-HTTPS; then
# add a forward rule for the traffic (same reason)
iptables -w -I FORWARD 2 -d 10.137.1.z -p tcp --dport 443 -m conntrack --ctstate NEW -j MY-HTTPS
fi
Note: Again in R4 the following needs to be added:
.. code:: bash
#############
# In Qubes R4
# If not already present
if nft -nn list table ip qubes-firewall | grep "tcp dport 443 ct state new"; then
# Add a filtering rule
nft add rule ip qubes-firewall forward meta iifname eth0 ip daddr 10.137.1.z tcp dport 443 ct state new counter accept
fi
**3. Route packets from the FirewallVM to the VM**
For the following example, we use the fact that the physical interface
of sys-firewall, facing sys-net, is eth0. Furthermore, we assume that
the target VM running the web server has the IP address 10.137.0.xx and
that the IP address of sys-firewall is 10.137.1.z.
In the sys-firewall VMs Terminal, code a natting firewall rule to route
traffic on its outside interface for the service to the qube
.. code:: bash
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -d 10.137.1.z -j DNAT --to-destination 10.137.0.xx
Code the appropriate new filtering firewall rule to allow new
connections for the service
.. code:: bash
iptables -I FORWARD 2 -i eth0 -s 192.168.x.0/24 -d 10.137.0.xx -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
Note: If you do not wish to limit the IP addresses connecting to the
service, remove the ``-s 192.168.0.1/24``
Note: On Qubes R4
.. code:: bash
nft add rule ip qubes-firewall forward meta iifname eth0 ip saddr 192.168.x.0/24 ip daddr 10.137.0.xx tcp dport 443 ct state new counter accept
Once you have confirmed that the counters increase, store these command
in ``/rw/config/qubes-firewall-user-script``
.. code:: bash
sudo nano /rw/config/qubes-firewall-user-script
.. code:: bash
#!/bin/sh
####################
# My service routing
# Create a new firewall natting chain for my service
if iptables -w -t nat -N MY-HTTPS; then
# Add a natting rule if it did not exist (to avoid clutter if script executed multiple times)
iptables -w -t nat -A MY-HTTPS -j DNAT --to-destination 10.137.0.xx
fi
# If no prerouting rule exist for my service
if ! iptables -w -t nat -n -L PREROUTING | grep --quiet MY-HTTPS; then
# add a natting rule for the traffic (same reason)
iptables -w -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -d 10.137.1.z -j MY-HTTPS
fi
######################
# My service filtering
# Create a new firewall filtering chain for my service
if iptables -w -N MY-HTTPS; then
# Add a filtering rule if it did not exist (to avoid clutter if script executed multiple times)
iptables -w -A MY-HTTPS -s 192.168.x.0/24 -j ACCEPT
fi
# If no forward rule exist for my service
if ! iptables -w -n -L FORWARD | grep --quiet MY-HTTPS; then
# add a forward rule for the traffic (same reason)
iptables -w -I FORWARD 4 -d 10.137.0.xx -p tcp --dport 443 -m conntrack --ctstate NEW -j MY-HTTPS
fi
################
# In Qubes OS R4
# If not already present
if ! nft -nn list table ip qubes-firewall | grep "tcp dport 443 ct state new"; then
# Add a filtering rule
nft add rule ip qubes-firewall forward meta iifname eth0 ip saddr 192.168.x.0/24 ip daddr 10.137.0.xx tcp dport 443 ct state new counter accept
fi
Finally make this file executable (so it runs at every Firewall VM
update)
.. code:: bash
sudo chmod +x /rw/config/qubes-firewall-user-script
If the service should be available to other VMs on the same system, do
not forget to specify the additional rules described above.
**4. Allow packets into the qube to reach the service**
Here no routing is required, only filtering. Proceed in the same way as
above but store the filtering rule in the ``/rw/config/rc.local``
script. For the following example, we assume that the target VM running
the web server has the IP address 10.137.0.xx
.. code:: bash
sudo nano /rw/config/rc.local
.. code:: bash
######################
# My service filtering
# Create a new firewall filtering chain for my service
if iptables -w -N MY-HTTPS; then
# Add a filtering rule if it did not exist (to avoid clutter if script executed multiple times)
iptables -w -A MY-HTTPS -j ACCEPT
fi
# If no input rule exists for my service
if ! iptables -w -n -L INPUT | grep --quiet MY-HTTPS; then
# add a forward rule for the traffic (same reason)
iptables -w -I INPUT 5 -d 10.137.0.xx -p tcp --dport 443 -m conntrack --ctstate NEW -j MY-HTTPS
fi
This time testing should allow connectivity to the service as long as
the service is up :-)
Where to put firewall rules
---------------------------
Implicit in the above example :doc:`scripts </user/advanced-topics/config-files>`, but
worth calling attention to: for all qubes *except* those supplying
networking, iptables commands should be added to the
``/rw/config/rc.local`` script. For app qubes supplying networking
(``sys-firewall`` inclusive), iptables commands should be added to
``/rw/config/qubes-firewall-user-script``.
Firewall troubleshooting
------------------------
Firewall logs are stored in the systemd journal of the qube the firewall
is running in (probably ``sys-firewall``). You can view them by running
``sudo journalctl -u qubes-firewall.service`` in the relevant qube.
Sometimes these logs can contain useful information about errors that
are preventing the firewall from behaving as you would expect.