The ideal service runs with the minimum set of privileges necessary to perform the task it is configured to do.
When writing new services, consider the following:
- Can the service run as an unprivileged user?
- In most cases, the daemon can run as an unprivileged user or with a limited set of super-user capabilities
- Can the service run without network access?
- Can the service operate with a private /dev hierarchy?
- Can the service operate with a private /tmp?
- Which parts of the filesystem does the service actually need to read/write?
The following sections illustrate how to achieve least privilege with SystemD.
All of these directives apply to the unit’s [Service]
specification,
exposed via systemd.services.<name?>.serviceConfig
.
PrivateDevices | private minimal device name space |
PrivateTmp | private /tmp |
ProtectHome | private /home and /run/user |
ProtectSystem | disallow modification of system directories |
PrivateNetwork | private minimal network stack, effectively disables access to the external network |
CapabilityBoundingset | define capabilities available to the executing process |
AmbientCapabilities | grant capabilities to an unprivileged daemon |
Note that most of these directives are merely convenient alternatives to more fine-grained options, such as:
InaccessibleDirectories | directories that are inaccessible |
ReadonlyDirectories | directories that are read-only |
AllowedDevices | private /dev containing only these devices |
- Use
PrivateDevices
whenever possible - Use
PrivateNetwork
unless the service cannot operate without network access - Use
PrivateHome
whenever possible, especially for long-running services; even if the daemon runs as an unprivileged user, DAC misconfiguration may allow it to read other user’s files. - Use
PrivateTmp
whenever possible, especially for long-running services. There is rarely a legitimate reason for one service to be able to read the temporary files of another (again, be mindful of DAC misconfiguration). - Run the daemon as an unprivileged user. If necessary, additional super-user capabilities can be added to the process ambient capability set.
First, create the system user that the daemon will run as:
{
users.groups.foo = { };
users.users.foo = { isSystemUser = true; group = "foo"; };
}
Then, configure systemd to run the daemon as the named user and group:
User=foo
Group=foo
Note: the working directory is set to the user’s home directory; use the
WorkingDirectory
directive to specify a different working directory.
Use case: running a daemon under an unprivileged user that can listen on
port 80. To do so, add cap_net_admin
to the daemon’s ambient capability set:
User=foo
AmbientCapabilities="cap_net_admin"
To grant additional capabilities to an existing NixOS service, set
systemd.services.<name?>.serviceConfig.AmbientCapabilities
, as in
{
services.transmission.enable = true;
services.transmission.port = 80;
systemd.services.transmission.serviceConfig.AmbientCapabilities = "cap_net_admin";
}
- man:systemd.directives(5) - master list of directives, useful starting point
- man:systemd.exec(5) - options pertaining to processes started by systemd units
- man:systemd.resource-control(5) - control system resource usage