From 88c580da3f5da5190e77f08182149b25bf8cc48f Mon Sep 17 00:00:00 2001 From: amy bones Date: Tue, 22 Jul 2025 17:33:47 -0700 Subject: [PATCH] phd: unit startup and hardening improvements Summary: These changes help ensure that phd starts more reliably and should phd itself, or one of its dependencies (such as `git`, or `hg`) become somehow compromised, the blast radius is minimized. - Improve the startup characteristics - use `syslog.socket` as `syslog.target` isn't guaranteed by systemd. - use `network-online.target` rather than `network.target` under the common assumption that mysql is on another host. - defensively order after `local-fs.target`. - Improve service hardening - `ProtectHome=tmpfs`: ensure that arbitrary home directories are invisible. - `ProtectHostname=yes`: prevent changing the system hostname. - `ProtectKernelLogs=yes`: prevent access to kernel (dmesg) logs. - `ProtectKernelModules=yes`: prevent access/modification to loaded modules. - `ProtectKernelTunables=yes`: prevent modification sysctls and some `/proc` parameters. - `ProtectSystem=full`: make `/boot`, `/efi`, `/usr`, and `/etc` read-only. - `ProtectProc=invisible`: hide other than itself and those it spawned from itself. - `ProtectControlGroups=strict`: hide other cgroups, and prevent modifications. - `PrivateDevices=yes`: only expose essential devices e.g. `/dev/null`. - `PrivateIPC=yes`: restrict mostly legacy IPC mechanisms (i.e., SysV IPC). - `PrivateTmp=yes`: give phd private temporary directories `/tmp`, `/var/tmp`. This helps make temporary files safer and improves service behavior since systemd will clean up these directories after every service stop. - `PrivateUsers=yes`: make only `root`, `nobody`, and the configured `daemon-user` visible. The `nobody` is mapped to the entire rest of the uid space, precluding even knowledge of other user account or groups. Test Plan: I deployed these units on my production install of Phorge and everything kept working. I used the following systemd drop-in specify my install's configuration: ```lang=ini,name=/etc/systemd/system/phorge-phd.service.d/override.conf [Service] User=phd Group=phd # repository storage root ReadWritePaths=/data # phd log directory ReadWritePaths=/var/log/phorge ``` Reviewers: avivey, O1 Blessed Committers Reviewed By: avivey, O1 Blessed Committers Subscribers: tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D26216 --- resources/phd/phorge-phd.service | 23 ++++++++++++++++++++--- resources/phd/phorge-phd@.service | 23 ++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/resources/phd/phorge-phd.service b/resources/phd/phorge-phd.service index c8931d8632..9cb129f2c2 100644 --- a/resources/phd/phorge-phd.service +++ b/resources/phd/phorge-phd.service @@ -1,17 +1,34 @@ [Unit] Description=Phorge Daemons Documentation=https://we.phorge.it/book/phorge/article/managing_daemons/ -After=syslog.target network.target +# Want this so that there is a greater chance phd will start up successfully +# when mysql is on a remote host. +Wants=network-online.target +After=local-fs.target network-online.target syslog.socket [Service] Type=forking EnvironmentFile=/etc/phorge/environment -User=phd -Group=phd +User=daemon-user +Group=daemon-user ExecStart="${PHORGE_ROOT}/bin/phd" start +ExecReload="${PHORGE_ROOT}/bin/phd" reload ExecStop="${PHORGE_ROOT}/bin/phd" stop Restart=on-failure +ProtectHome=tmpfs +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=full +ProtectProc=invisible +ProtectControlGroups=strict +PrivateDevices=yes +PrivateIPC=yes +PrivateTmp=yes +PrivateUsers=yes + [Install] WantedBy=multi-user.target diff --git a/resources/phd/phorge-phd@.service b/resources/phd/phorge-phd@.service index f6d9d79761..775a1d7ba7 100644 --- a/resources/phd/phorge-phd@.service +++ b/resources/phd/phorge-phd@.service @@ -1,19 +1,36 @@ [Unit] Description=Phorge Daemons Documentation=https://we.phorge.it/book/phorge/article/managing_daemons/ -After=syslog.target network.target +# Want this so that there is a greater chance phd will start up successfully +# when mysql is on a remote host. +Wants=network-online.target +After=local-fs.target network-online.target syslog.socket [Service] Type=forking Environment=PHABRICATOR_ENV=%I EnvironmentFile=/etc/phorge/environment EnvironmentFile=-/etc/phorge/%I.env -User=phd -Group=phd +User=daemon-user +Group=daemon-user ExecStart="${PHORGE_ROOT}/bin/phd" start +ExecReload="${PHORGE_ROOT}/bin/phd" reload ExecStop="${PHORGE_ROOT}/bin/phd" stop Restart=on-failure +ProtectHome=tmpfs +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=full +ProtectProc=invisible +ProtectControlGroups=strict +PrivateDevices=yes +PrivateIPC=yes +PrivateTmp=yes +PrivateUsers=yes + [Install] WantedBy=multi-user.target -- 2.51.2