Skip to content

Instantly share code, notes, and snippets.

@joachifm
Last active September 13, 2021 18:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joachifm/022ca74fd447bd8bb2f80a133c0ab3a9 to your computer and use it in GitHub Desktop.
Save joachifm/022ca74fd447bd8bb2f80a133c0ab3a9 to your computer and use it in GitHub Desktop.
services

Service hardening basics

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.

Important service directives

All of these directives apply to the unit’s [Service] specification, exposed via systemd.services.<name?>.serviceConfig.

PrivateDevicesprivate minimal device name space
PrivateTmpprivate /tmp
ProtectHomeprivate /home and /run/user
ProtectSystemdisallow modification of system directories
PrivateNetworkprivate minimal network stack, effectively disables access to the external network
CapabilityBoundingsetdefine capabilities available to the executing process
AmbientCapabilitiesgrant capabilities to an unprivileged daemon

Note that most of these directives are merely convenient alternatives to more fine-grained options, such as:

InaccessibleDirectoriesdirectories that are inaccessible
ReadonlyDirectoriesdirectories that are read-only
AllowedDevicesprivate /dev containing only these devices

General recommendations

  • 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.

Running as an unprivileged user

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.

Granting additonal capabilities to unprivileged daemons

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";
}

References

  • 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment