# Tip : systemd, capabilities and rootless nginx, radvd, tor

## serafean

Hi all,

I wanted to try out how powerful systemd unit files are, so came up with this challenge : start nginx rootless, with as less access as possible, listening on ports 80 and 443.

Software versions : systemd-229, linux-4.5, nginx 1.8.1

The unit file:

```
[Unit]

Description=The nginx HTTP and reverse proxy server

After=network.target remote-fs.target nss-lookup.target

[Service]

User=nginx

Group=nginx

Type=forking

PIDFile=/var/run/nginx/nginx.pid

ExecStartPre=/usr/sbin/nginx -t

ExecStart=/usr/sbin/nginx

ExecReload=/bin/kill -HUP $MAINPID

ExecStop=/bin/kill -QUIT $MAINPID

#Security

CapabilityBoundingSet=CAP_NET_BIND_SERVICE

AmbientCapabilities=CAP_NET_BIND_SERVICE

ReadOnlyDirectories=/etc/ssl/nginx

ReadWriteDirectories=/var/log/nginx /var/www/

PrivateTmp=yes

PrivateDevices=yes

ProtectSystem=full

ProtectHome=yes

NoNewPrivileges=yes

[Install]

WantedBy=multi-user.target
```

First off was giving nginx the capability to bind ports 443 and 80, this was done with AmbientCapabilities and CapabilityBoundingSet.

Then locking it out of the rest of the system.

PrivateDevices - Hides all device nodes (except for random, null, and some others)

PrivateTmp - mounts a separate tmp that is used only by nginx (namespaces are used AFAIK)

ProtectHome - Disables access to /home and /root

ProtectSystem - enables only read access to most of the FS

Finally, poking holes in the lock :

ReadOnlyDirectories - disable writing to dir containing SSL certificates. Since running as user nginx requires all these to have nginx of their owner, nginx could theoretically overwrite its certificates. This prevents that.

ReadWriteDirectories - Allow rw access to log directory, and to data directory.

It works, I haven't found anything broken (yet).

This concept is reusable : I've adapted it to tvheadend, dnsmasq and radvd. I'm hoping to convert more of my unit files so as to have less services dependend on them dropping their privileges.

The greatest pain are PID files : /run isn't writeable by non-root processes, so for each such a service, there has to exist an entry in /etc/tmpfiles.d creating /run/${SERVICE_NAME}/ with appropriate access rights, so the PID file can be written somewhere.

Here's to hoping someone finds this useful  :Smile: 

Comments very welcome.

Serafean.Last edited by serafean on Wed Nov 30, 2016 2:16 pm; edited 1 time in total

----------

## serafean

After a week running in this mode, I deem it usable  :Smile: 

Joining dnsmasq.service.

```
[Unit]

Description=A lightweight DHCP and caching DNS server

After=network.target

[Service]

User=dnsmasq

Group=dnsmasq

Type=simple

PIDFile=/var/run/dnsmasq/dnsmasq.pid

ExecStartPre=/usr/sbin/dnsmasq --test

ExecStart=/usr/sbin/dnsmasq -k -x /var/run/dnsmasq/dnsmasq.pid

ExecReload=/bin/kill -HUP $MAINPID

#Security

CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN

AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN

PrivateTmp=yes

PrivateDevices=yes

ProtectSystem=full

ProtectHome=yes

NoNewPrivileges=yes

[Install]

WantedBy=multi-user.target
```

And radvd.service

```
[Unit]

Description=Router advertisement daemon for IPv6

Documentation=man:radvd(8)

After=network.target

[Service]

User=radvd

Group=radvd

Type=forking

ExecStartPre=/usr/sbin/radvd --configtest

ExecStart=/usr/sbin/radvd --logmethod stderr --debug 0

ExecReload=/usr/sbin/radvd --configtest ; \

           /bin/kill -HUP $MAINPID

PIDFile=/run/radvd/radvd.pid

# Performance

CPUSchedulingPolicy=idle

#Hardening

CapabilityBoundingSet=CAP_NET_BIND_SERVICE  CAP_NET_RAW

AmbientCapabilities=CAP_NET_BIND_SERVICE  CAP_NET_RAW

PrivateTmp=yes

PrivateDevices=yes

ProtectSystem=full

ProtectHome=yes

NoNewPrivileges=yes

[Install]

WantedBy=multi-user.target
```

----------

## candrews

I've submitted bugs for improving these systemd units, and included pull requests:

dnsmasq: https://bugs.gentoo.org/show_bug.cgi?id=587586

radvd: https://bugs.gentoo.org/show_bug.cgi?id=587588

In the future, you may want to do the same - having these changes made in Gentoo means you don't have to maintain them and everyone gets the benefits.

----------

## Ant P.

Huh, makes me wonder why something as simple as radvd doesn't have a USE=caps for that, it really shouldn't run as root. Quagga does that already...

----------

## serafean

 *candrews wrote:*   

> 
> 
> In the future, you may want to do the same - having these changes made in Gentoo means you don't have to maintain them and everyone gets the benefits.

  Oh wow! I wasn't thinking that far out  :Smile:  I was just playing around xD

Thanks for taking the time to report those enhancments.

I read throught the bug reports,  and if someone could enlighten me as to why starting a process as root and letting it drop its capabilities is better than this, I'd be very grateful.

----------

## serafean

And another one for tor :

```
[Unit]

Description=The Onion Router

After=network-online.target

[Service]

User=tor

Group=tor

ExecStartPre=/usr/bin/tor --verify-config -f /etc/tor/torrc

ExecStart=/usr/bin/tor --RunAsDaemon 0 -f /etc/tor/torrc

ExecReload=/bin/kill -HUP $MAINPID

KillSignal=SIGINT

TimeoutStopSec=32

LimitNOFILE=30000

# Hardening options:

#CapabilityBoundingSet = CAP_NET_BIND_SERVICE

# Capabilities aren't enough to have ports < 1024

RuntimeDirectory=tor

RuntimeDirectoryMode=0700

ReadWriteDirectories=/var/lib/tor/

PrivateTmp = yes

PrivateDevices = yes

ProtectHome = yes

ProtectSystem = strict

NoNewPrivileges = yes

[Install]

WantedBy=multi-user.target
```

will be submitting it to bugzilla[/code]

----------

## ssokolow

I decided to develop something for my Debian VPS using your nginx.service as a base and I managed to lock it down even further (and make the service file easier to use) by making the following tweaks:

 Instead of replicating everything, use .include /lib/systemd/system/nginx.service at the top of the file as described here.

 Instead of using tmpfiles.d, use RuntimeDirectory=nginx inside the service file and set PIDFile and the pid line in nginx.conf to /run/nginx/nginx.pid

Another tweak I'm planning to do is to set nginx to log via syslog(), configure my syslogd to split the entries out into nginx/access.log and nginx/error.log and then remove the ReadWriteDirectories line. (That way, if someone compromises nginx, they won't have the ability to modify its logs to cover their tracks. The manual way to do this is to use chattr +a to set the logs append-only and then modify your logrotate config to temporarily unset that attr.)

Using syslog also has the added benefit that it's easy to forward logs to a central location if you have more than one server. (I use it to have my FreeBSD router log to my desktop Linux PC's syslog infrastructure so it's easy to keep long-term audit logs.)

It may also make it feasible to ask systemd to run nginx under a random/temporary UID and GID.

----------

