Network Isolation of Services with Systemd

This tutorial will look at how network namespaces can be defined in systemd service unit definitions. This example will at running the Nginx service inside a separate network namespace that has its own physical interface. Like most of my tutorials, this will be done on a Fedora system.

What are Network Namespaces?

Network namespaces are an important component of containerization in Linux. A network namespace (netns) allows a running process to see different network interfaces, routes, and firewall rules from the rest  of the system. There are a number of use cases for network namespaces, such as running multiple servers, testing/staging environments and providing isolation of services.

Creating a Network Namespace

We begin by creating a generic systemd service for creating a named network namespace. I add the following to /usr/lib/systemd/system/netns@.service. In systemd, the @ mean the service takes a parameter which is passed to the unit via %i. E.g, we can run sudo systemctl start netns@webserver.service.

Unit to set up the interface

Next, we create a definition in /usr/lib/systemd/system/attach-enp3s1f0@.service. This service associates the enp3s1f0 with a specified network namespace. It also sets up addresses within  the network namespace. In iproute2, the command to run a process within a  specified network namespace is ip netns exec $namespace $command.

Running a process inside a network namespace

As a simple test, I define /usr/lib/systemd/system/webserver.service which runs a simple TCP server over netcat inside the netns. Notice the JoinsNamespaceOf=netns@webserver.service option, stating that the service join the network namespace of an already running service. I added some extra privilege constraint such as ProtectSystem=true and CapabilityBoundingSet= which are unrelated to network namespaces - these are for another post :)

Trying it out

After setting the above files, I run the following commands

$ sudo systemctl start \
  netns@webserver.service \
  attach-enp3s1f0@webserver.service \
  webserver.service

Now the enp3s1f0 interface has disappeared from the system, as it's no longer in the default namespace

$sudo ip l show dev enp3s1f0
Device "enp3s1f0" does not exist.

Let's see if its reachable

$ curl http://192.168.0.80:8080
OK

When a service is running in an alternate network namespace, it is possible to use the service's port on the host system, over all  interfaces.

$ /usr/bin/nc --send-only --exec "/usr/bin/echo Foo" -lkp 8080 &
[1] 8556
$ curl http://0.0.0.0:8080
Foo
$ curl http://192.168.0.80:8080
OK

Services running in an alternate network namespace are unaffected by local firewall rules on the host system.

$ sudo iptables -t raw -I PREROUTING -p tcp --dport 8080 -j DROP
$ curl --max-time=3 http://0.0.0.0:8080
curl: (28) Connection timed out after 3001 milliseconds
$ curl http://192.168.0.80:8080

Associating the nginx service with the network namespace

I add PrivateNetworking=true to the [Service] section and the following lines to the [Unit] section of /usr/lib/systemd/system/nginx.service

Requires=webserver.service
After=webserver.service
JoinsNamespaceOf=netns@webserver.service

I then run sudo systemctl daemon-reload; sudo systemctl start nginx.service.

Let's test it out

$ curl 192.168.0.80
<!doctype html>
<html>
 <head>
  <title>Nginx Server</title>
 </head>
 <body>
  <h1>It Works!</h1>
 </body>
</html>

Conclusion

A couple of final things to note. Network namespaces are a form of  specific isolation - they only concern networking, not filesystems, user  rights, etc. These partial isolation systems can be joined to form  general isolation, this is how Docker and containerd operate. Systemd and the related technologies on a modern Linux system are extremely powerful and there's a lot of isolation that can be done within systemd service definitions, such as privilege and capability dropping, which will be covered in more detail in a later post.

Standalone use of network namespaces can be useful for situations  when the only form of isolation required is on the networking side. For example, running a service (such as the transmission-daemon bittorrent) client over a VPN, without requiring a VPN on the rest of the system.

I hope you found this post informative