This How-To aims at bringing some help leveraging some systemd niceties to enforce security restrictions on daemons (or services in systemd parlance) such as Apache HTTPD and PHP-FPM. Instructions are tested on GNU/Linux Debian. They should work on any Debian-derived distribution, or with some tweaks here and there, to any GNU/Linux distribution.
Explanations
Wether you like it or not, and wether these features are relevant for a PID 1 process or not, systemd does offer some nice-to-have functionalities like overrides and parameters that can help you administer and secure your system, in a way that a traditional init system wouldn’t.
Parameters
Parameters are key-value INI-file like settings that systemd uses to define the execution environment of the processes that it will spawn.
Override
On systems with systemd, daemons are spawned and monitored using unit configuration files. These files define the execution environment of processes that get spawned as part of the unit.
Upstream projects and distributions offer configuration files along with the software, and gets updated either by upstream when software changes or distribution to better integrate it with the rest of the system.
Overrides are a way for system administrators to add even more parameters to the unit without having to modify the system-provided configuration file.
Typically, one would leave the task of bringing up the daemon to a working state to the system-provided configuration file, which will be updated when the system gets updated, and define his own parameters in an override file that are relevant for his use case.
For more information about this, refer to the systemd.exec(5) man page.
Limitations
These hardening measures will only be enforced by systemd as long as the processes are in its scope. If an Apache HTTPD process is executed and daemonized directly from a CLI, they will be ineffective. For such cases, creating and curating sandbox profiles for AppArmor or SELinux remains the best option.
Use Case
In this how-to, we’ll briefly discuss how to use those features to quickly and effectively enforce some security measures on processes that are usually reachable through firewalls : Apache HTTP and PHP-FPM.
Steps
To create an override file for a service, use:
# systemctl edit myawesomeprogram.service
systemd will then merge the system configuration file and the override file when managing the process.
Below are the overrides I personally use for Apache HTTPD and PHP-FPM. Here are some of the parameters I define and that I have curated for my use case:
- Capabilities: leverage the capabilities(7) from the Linux kernel to lock down what the process is allowed to do, even as root;
- NoNewPrivileges: the process cannot get new privileges ever again, like with setuid();
- Protect[Something]: restrict what the process can read or write on the system, even if it is privileged;
- PrivateDevices: the process will see a pseudo /dev mount that only contains Linux Kernel API devices like /dev/random;
- PrivateTmp: the process will see distincts /tmp and /var/tmp directories mounted on a virtual filesystem, isolating the system’s temp files from it;
- ReadOnlyPaths/ReadWritePaths: further restrict the read/write privileges of the process even if, at a filesystem level, it is allowed to read/write those paths. In my case, I only allow paths that the process has to write to or else the service crashes;
- NoExecPaths/ExecPaths: absolute paths to executables that the process will (or will not) be able to execute. In my case, I only allow executables that the process requires or else the service crashes.
For more information about this, again, refer to the systemd.exec(5) man page.
Also, if you allow file uploads or CMS auto updates, be sure to include your webroot path to the list of read write paths.
Override for Apache HTTPD
[Service] CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK LockPersonality=yes NoNewPrivileges=yes MemoryDenyWriteExecute=yes KeyringMode=private ProtectProc=invisible ProtectControlGroups=yes ProtectHostname=yes ProtectSystem=strict ProtectHome=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectClock=yes PrivateDevices=yes PrivateTmp=yes RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 RestrictNamespaces=~mnt RestrictRealtime=yes RestrictSUIDSGID=yes ReadOnlyPaths=/ ReadWritePaths=[webroot] /var/cache/apache2 /var/log/apache2 /var/lib/apache2 /var/lock /var/run /run InaccessiblePaths=-/boot -/lost+found -/etc/default -/etc/php -/etc/apt -/etc/shadow -/etc/sudoers -/etc/sysctl.conf -/etc/sysctl.d -/var/backups -/var/mail -/var/spool -/var/local -/var/cache/apt -/var/opt NoExecPaths=/ ExecPaths=/usr/bin/sh /usr/bin/chmod /usr/bin/chown /usr/bin/mv /usr/bin/mktemp /usr/bin/mkdir /usr/bin/rmdir /usr/bin/rm /usr/bin/test /usr/bin/id /usr/sbin/apachectl /usr/sbin/apache2 /usr/lib /usr/lib64 SystemCallArchitectures=native SystemCallErrorNumber=EPERM SystemCallFilter=@system-service
Override for PHP-FPM
[Service] CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK CAP_DAC_OVERRIDE AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_CHOWN CAP_KILL CAP_IPC_LOCK CAP_DAC_OVERRIDE LockPersonality=yes NoNewPrivileges=yes KeyringMode=private ProtectProc=invisible ProtectControlGroups=yes ProtectHostname=yes ProtectSystem=strict ProtectHome=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectClock=yes PrivateDevices=true PrivateTmp=true RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 RestrictNamespaces=~mnt RestrictRealtime=yes RestrictSUIDSGID=yes ReadOnlyPaths=/ ReadWritePaths=[webroot] /var/log/php[version]-fpm.log /var/run/php /var/lib/php /run/php InaccessiblePaths=-/boot -/lost+found -/etc/default -/etc/apache2 -/etc/apt -/etc/shadow -/etc/sudoers -/etc/sysctl.conf -/etc/sysctl.d -/var/backups -/var/mail -/var/spool -/var/local -/var/cache -/var/opt NoExecPaths=/ ExecPaths=/usr/bin/sh /usr/bin/sed /usr/bin/update-alternatives /usr/sbin/php-fpm[version] /usr/bin/php /usr/lib /usr/lib64 SystemCallArchitectures=native SystemCallErrorNumber=EPERM SystemCallFilter=@system-service
I’d like to hear from you!
Security is an endless job, it’s exhausting, it’s frightening, and thinking that the defenses you put on are perfect and got your ass 100% covered is a shot in the foot in the dark ages of ransomware and whatnot.
So, I’d like to hear from you! What do you think of these overrides? Do you also use them to secure your services? Do you use even more parameters? Do you prefer AppArmor or SELinux profiles? Why?
I’d like to know 🙂 Comments are down below, and my e-mail address is on the About Me page.