Skip to main content

How to restrict network access in Podman with systemd

Systemd's RestrictAddressFamilies directive can limit an intruder's privilege and thus their ability to launch attacks on your devices.
Image
Empty tin can containers

My previous article demonstrated that a socket-activated container can use an activated socket to serve the internet even when the network is disabled through the option --network=none for podman run. This article takes this idea one step further by also restricting internet access for Podman and its helper programs such as conmon and the OCI runtime.

To follow along with the examples, you must have:

  • Podman 3.4.0 or above
  • runc 1.1.3 or crun 1.5 or above
  • container-selinux 2.183.0 or above (if you're using SELinux)

When using Podman in a systemd service, the systemd directive RestrictAddressFamilies can be used to restrict Podman's access to sockets. This restriction concerns only the use of the system call socket(), meaning that socket-activated sockets are unaffected by the directive.

[ Download now: Podman basics cheat sheet ]

Podman can still run containers that need internet access only through socket-activated sockets when systemd is configured to restrict Podman's ability to use the system call socket() for the AF_INET and AF_INET6 socket families. Podman would then be blocked from pulling any container images, so the container image must be present beforehand.

Restrict a socket-activated echo server

You can probably see how RestrictAddressFamilies with a simple echo server container supports socket activation.

If the --pull=never option is added to podman run, the echo server container continues to work even with the very restricted setting:

RestrictAddressFamilies=AF_UNIX AF_NETLINK

All use of the system call socket() is then disallowed except for the AF_UNIX sockets and AF_NETLINK sockets.

If there were a security vulnerability in Podman, conmon, or the OCI runtime, this configuration limits the avenues an intruder has to attempt to launch attacks on other devices on the network.

Create the systemd unit files

Create the container:

$ podman pull \
-q ghcr.io/eriksjolund/socket-activate-echo
6f68cb020b4b04e7d124df6c1bc60547e44b987223ac93d00515cc1412a7bc9a

$ podman create --rm --name restricted-echo \
--network=none --pull=never ghcr.io/eriksjolund/socket-activate-echo
f1f8ecd129c33012dac0be81dfdbd2a269295d322fd1ffd818fe8672ee0238ee

Generate the systemd service unit:

$ mkdir -p ~/.config/systemd/user

$ podman generate systemd --name \
--new restricted-echo > ~/.config/systemd/user/restricted-echo.service

Add the following two lines in the [Service] section:

$ sed -i '/\[Service\]/a \
RestrictAddressFamilies=AF_UNIX AF_NETLINK\
NoNewPrivileges=yes' ~/.config/systemd/user/restricted-echo.service

Add the following two lines in the [Unit] section:

$ sed -i '/\[Unit\]/a \
After=podman-usernamespace.service\
Requires=podman-usernamespace.service' ~/.config/systemd/user/restricted-echo.service

Create the file ~/.config/systemd/user/restricted-echo.socket and paste this into it:

[Unit]
Description=restricted echo server

[Socket]
ListenStream=127.0.0.1:9000

[Install]
WantedBy=sockets.target

Create the file ~/.config/systemd/user/podman-usernamespace.service with these contents:

[Unit]
Description=podman-usernamespace.service

[Service]
Type=oneshot
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman unshare /bin/true
RemainAfterExit=yes

Test the echo server

After editing the unit files, reload systemd:

$ systemctl --user daemon-reload

Next, start the socket unit:

$ systemctl --user start restricted-echo.socket

Finally, test the echo server with the program socat:

$ echo hello | socat - tcp4:127.0.0.1:9000
hello

The echo server works as expected! It replies with hello after receiving the text hello.

Podman is blocked from establishing new connections to the internet, but everything works as expected because Podman is configured to not attempt to pull the container image.

[ Thinking about security? Check out this guide to boosting hybrid cloud security and protecting your business. ]

Now modify the service unit so that Podman always pulls the container image:

$ grep -- --pull= ~/.config/systemd/user/restricted-echo.service
	--pull=never ghcr.io/eriksjolund/socket-activate-echo

$ sed -i s/pull=never/pull=always/ ~/.config/systemd/user/restricted-echo.service

$ grep -- --pull= ~/.config/systemd/user/restricted-echo.service
	--pull=always ghcr.io/eriksjolund/socket-activate-echo

After editing the unit file, systemd needs to reload its configuration:

$ systemctl --user daemon-reload

Stop the service:

$ systemctl --user stop restricted-echo.service

Test the echo server with the program socat:

$ echo hello | socat - tcp4:127.0.0.1:9000

As expected, the service fails because Podman is blocked from establishing a connection to the container registry.

Use journalctl to see the related error message:

$ journalctl --user -xe -u restricted-echo.service | grep -A2 "Trying to pull" | tail -3
Jul 16 08:26:10 asus podman[28272]: Trying to pull ghcr.io/eriksjolund/socket-activate-echo:latest...
Jul 16 08:26:10 asus podman[28272]: Error: initializing source docker://ghcr.io/eriksjolund/socket-activate-echo:latest: pinging container registry ghcr.io: Get "https://ghcr.io/v2/": dial tcp 140.82.121.34:443: socket: address family not supported by protocol
Jul 16 08:26:10 asus systemd[10686]: test.service: Main process exited, code=exited, status=125/n/a

The service and socket are marked as failed:

$ systemctl --user is-failed restricted-echo.service
failed

$ systemctl --user is-failed restricted-echo.socket
failed

Revert the change and use --pull=never instead:

$ sed -i s/pull=always/pull=never/ \
~/.config/systemd/user/restricted-echo.service

$ systemctl --user daemon-reload

$ systemctl --user reset-failed restricted-echo.service

$ systemctl --user reset-failed restricted-echo.socket

$ systemctl --user start restricted-echo.socket

Use a separate service for creating the user namespace

Consider a situation where systemd starts the systemd user services for a user directly after a reboot. Assume that lingering has been enabled for the user with loginctl enable-linger USERNAME and that the user is not logged in. The Podman systemd user service that starts first detects that the Podman user namespace is missing and tries to create it. This normally succeeds, but when RestrictAddressFamilies is used together with rootless Podman, it fails.

The reason is that using RestrictAddressFamilies in an unprivileged systemd user service implies NoNewPrivileges=yes. This prevents /usr/bin/newuidmap and /usr/bin/newgidmap from running with elevated privileges. Podman executes newuidmap and newgidmap to set up user namespace. Both executables normally run with elevated privileges, as they need to perform operations not available to an unprivileged user. These capabilities are:

$ getcap /usr/bin/newuidmap
/usr/bin/newuidmap cap_setuid=ep

$ getcap /usr/bin/newgidmap
/usr/bin/newgidmap cap_setgid=ep

You just need to set up the user namespace once because the created user namespace is reused for all other invocations of Podman. You can make services using RestrictAddressFamilies or NoNewPrivileges=yes work by configuring them to start after a systemd user service that is responsible for setting up the user namespace.

For instance, the unit restricted-echo.service depends on podman-usernamespace.service:

$ grep podman-usernamespace.service ~/.config/systemd/user/restricted-echo.service
After=podman-usernamespace.service
Requires=podman-usernamespace.service

The service podman-usernamespace.service is a Type=oneshot service that executes podman unshare /bin/true. This command is normally used for other things, but a side effect of the command is that it sets up the user namespace.

[ Improve your skills managing and using SELinux with this helpful guide. ]

Enable the socket unit and reboot:

$ systemctl --user enable restricted-echo.socket

$ sudo reboot

After the reboot, test the echo server with the program socat:

$ echo hello | socat - tcp4:127.0.0.1:9000
hello

The echo server works as expected even after a reboot!

Note that using the systemd directive RestrictAddressFamilies to restrict Podman is probably not strictly supported in Podman, as it's not mentioned in Podman documentation.

Wrap up

The systemd directive RestrictAddressFamilies provides a way to restrict network access for Podman and its helper programs, while a socket-activated echo server container can still serve the internet. One use case for this is running a socket-activated web server container so that Podman and its helper programs run with few privileges. If they are compromised due to a security vulnerability, the intruder would gain few privileges, having fewer opportunities to use the compromise as a starting point for attacks on other devices.

Topics:   Security   Podman   Containers  
Author’s photo

Erik Sjölund

Erik Sjölund enjoys learning and discovering new things, especially within container technologies. He holds a master's degree in Engineering Physics and has worked as a Linux sysadmin and software developer, especially in the field of life sciences. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.