Sandbox Systemd Units

SysD seems to be unstoppable in the Linux world. I personally have made peace with it.

I even appreciate the logging functions with journalctl, for example, and think they are much more pleasant than the confusing and insecure Syslog.

It would go beyond the scope of this article to explain everything, so here is just a brief outline:

  • Systemd is configured with unit files.
  • Most programs store their service files in /etc/system/system/
  • There are service files, timer configurations, device and routine configurations.
  • Each unit creates a journal, which can be read out with journalctl.

If you look at any unit file i /etc/systemd/system/ you will always see the same structure of [Unit], [Service] and [Install].

Example configuration at NGINX:

[Unit]
Description=A high performance web server \
and a reverse proxy server
Documentation=man:nginx(8)
After=network.target \
nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; \
master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; \
master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; \
master_process on;' \
-s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop \
--retry QUIT/5 \
--pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed


[Install]
WantedBy=multi-user.target
/etc/systemd/system/nginx.service

SystemD has an in-house exposure level function called systemd-analyze security which can be used to check the security settings:

systemd-analyze security nginx.service

NGINX gets the value 9.6 and is considered unsafe.

→ Overall exposure level for nginx.service: 9.6 UNSAFE 😨
systemd-analyze security

But what this output also does is display all the flags that make it unsafe.

Almost every flag is a boolean value. Here you have to try around until you get everything on green.

 NAME
✗ NoNewPrivileges=
DESCRIPTION
Service processes may acquire new privileges
EXPOSURE
0.2
NoNewPrivileges

Not every service needs to be run with root privileges. With NGINX, this is only required for port binding.

This right can be borrowed from root:

AmbientCapabilities=CAP_NET_BIND_SERVICE

With this setting NGINX can now be started as www-data.

My service file now looks like this after testing forever with systemctl edit --full nginx.service and  journalctl -fu nginx.service:

[Unit]
Description=A high performance web server \
and a reverse proxy server
Documentation=man:nginx(8)
After=network.target nss-lookup.target

[Service]
User=www-data
Group=www-data
Type=forking
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; \
master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; \
master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; \
master_process on;'-s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop \
--retry QUIT/5 --pidfile /run/nginx/nginx.pid
TimeoutStopSec=5
KillMode=mixed
AmbientCapabilities=CAP_NET_BIND_SERVICE
RuntimeDirectory=nginx
# You need to edit /etc/nginx/ngix.conf before
PIDFile=/run/nginx/nginx.pid
ProtectSystem=true
ProtectHome=true
SystemCallFilter=@system-service
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictSUIDSGID=true
KeyringMode=private
ProtectClock=true
RestrictRealtime=true
PrivateDevices=true
PrivateTmp=true
PrivateIPC=true
ProtectHostname=true
ProtectProc=true
ProtectSystem=true
CapabilityBoundingSet=~CAP_BLOCK_SUSPEND
CapabilityBoundingSet=~CAP_LEASE
CapabilityBoundingSet=~CAP_SYS_PACCT
CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG

[Install]
WantedBy=multi-user.target
/etc/systemd/system/nginx.service

My exposure level has dropped from just under 10 to 5.

→ Overall exposure level for 
nginx.service: 5.0 MEDIUM 😐
systemd-analyze security

The smiley looks less sad now.

You can do that with any unit file!

Beware

Like any sandbox, you can break out of it with enough zeal. SystemD sandboxing, for example, only checks the interprocess communication (IPC) to a simple degree, through which a process can break out of the barrier via BUS, for example.
Either way, however, the sandboxing argument is a huge step towards security improvement and makes it harder for attackers to take over your system.